Onimusha puzzle script

From Schmid.wiki
Jump to: navigation, search
class Board
    def initialize(tiles) @tiles = tiles end
    def solved?
        # ?a means the ASCII value of 'a' (97)
	@tiles[1][1] == ?a and @tiles[1][2] == ?b and
	@tiles[2][1] == ?c and @tiles[2][2] == ?d
    end
    # moves:   12
    #         8  3
    #         7  4
    #          65
    def move(n)
	if legal_move(n) then
	    case n
	    when 1: @tiles[3][1] = @tiles[2][1];@tiles[2][1] = @tiles[1][1]
		    @tiles[1][1] = @tiles[0][1];@tiles[0][1] = " "
	    when 2: @tiles[3][2] = @tiles[2][2];@tiles[2][2] = @tiles[1][2]
		    @tiles[1][2] = @tiles[0][2];@tiles[0][2] = " "

	    when 3: @tiles[1] = @tiles[1][1..3] + " "
	    when 4: @tiles[2] = @tiles[2][1..3] + " "

	    when 5: @tiles[0][2] = @tiles[1][2];@tiles[1][2] = @tiles[2][2]
		    @tiles[2][2] = @tiles[3][2];@tiles[3][2] = " "
	    when 6: @tiles[0][1] = @tiles[1][1];@tiles[1][1] = @tiles[2][1]
		    @tiles[2][1] = @tiles[3][1];@tiles[3][1] = " "

	    when 7: @tiles[2] = " " + @tiles[2][0..2]
	    when 8: @tiles[1] = " " + @tiles[1][0..2]
	    end
	else
	    puts "illegal move"
	end
    end
    # a move is legal if the tile on the opposite side is empty
    def legal_move(n)
        empty = ?\s # ASCII value of space (32)
	case n
	when 1: return @tiles[3][1] == empty
	when 2: return @tiles[3][2] == empty
	when 3: return @tiles[1][0] == empty
	when 4: return @tiles[2][0] == empty
	when 5: return @tiles[0][2] == empty
	when 6: return @tiles[0][1] == empty
	when 7: return @tiles[2][3] == empty
	when 8: return @tiles[1][3] == empty
	else return false
	end
    end
    def get_legal_moves
	legal_moves = []
	(1..8).each do |x| legal_moves += [x] if legal_move(x) end
	return legal_moves
    end
    # non-shallow copy
    def copy() return Marshal.load(Marshal.dump(self)) end
    def to_s()
	"          .- 12 -.\n" +
	"          | #{@tiles[0]} |\n" +
        "          8 #{@tiles[1]} 3\n" +
        "          7 #{@tiles[2]} 4\n" +
        "          | #{@tiles[3]} |\n" +
	"          '- 65 -'"
    end
end

class Brain
    attr_reader :moves_attempted
    def initialize(board) @original_board = board end
    def solve(limit)
        @moves_attempted = 0
        @solved = false
        recursive_descent(@original_board, [], limit)
        return @solved
    end
    # recursive brute force, tries 4^limit times before giving up
    def recursive_descent(board, moves_so_far, limit)
        return if moves_so_far.length >= limit or @solved
        board.get_legal_moves.each do |n|
            @moves_attempted+=1
            new_board = board.copy
            new_board.move(n)
            if new_board.solved? then
                @solved = true
                @solution = moves_so_far + [n]
                return
            else
                solution = recursive_descent(new_board, moves_so_far + [n], limit)
            end
        end
    end
    def print_solution
        puts "SOLVED in #{@solution.length} moves (#{@moves_attempted} moves attempted): "
        board = @original_board.copy
        @solution.each do |m|
            board.move(m)
            puts "move: #{m}"
            puts board
        end
        print "summary - moves: "
        @solution[0..-2].each do |m| print "#{m}, " end
        puts @solution[-1]
    end
end

board = Board.new( [ "  d " ,
		     "c.. " ,
		     " ..a" ,
		     " b  " ] )
puts board
brain = Brain.new(board)
# _extremely_ inefficient way of determining the smallest number of moves
(1..12).each do |moves|
    if brain.solve(moves) then
        brain.print_solution
        exit
    else
        puts "unsolvable in #{moves} moves (#{brain.moves_attempted} moves attempted)"
    end
end
Personal tools