def test_greedy_dfs(weights): def compare(original, final, moves, row, column, assert_error_message): for delta_r, delta_c in moves: original.swap(row, column, row + delta_r, column + delta_c) row += delta_r column += delta_c for r in range(original.rows): for c in range(original.columns): assert isinstance(original.cell(r, c), type(final.cell(r, c))), assert_error_message # solution should contain move list that gets you to the final board state starting from original board state original = Board.create_randomized_board(5, 6) _, moves, final_board = GreedyDfs(weights).solve(original, 20) compare(original, final_board, moves[1:], moves[0][0], moves[0][1], "Greedy DFS yielded an incorrect 4-way move list!") # same with diagonals enabled original = Board.create_randomized_board(5, 6) h = GreedyDfs(weights) h.diagonals = True _, moves, final_board = h.solve(original, 20) compare(original, final_board, moves[1:], moves[0][0], moves[0][1], "Greedy DFS yielded an incorrect 8-way move list!")
def test_cell_swap(board_with_1_chain): # swapping a board's cell with another should only affect the source and target cells original = board_with_1_chain swapped_board = Board.copy_board(original) # swap corners where the two pieces are different swapped_board.swap(0, 0, 4, 5) matching = [ swapped_board.cell(r, c) for c in range(original.columns) for r in range(original.rows) if isinstance(original.cell(r, c), type(swapped_board.cell(r, c))) and original.cell(r, c).location == swapped_board.cell(r, c).location ] assert len(matching) == 28, "Swapping upper left corner with lower right corner of board yielded match counts!" assert swapped_board.cell(0, 0).location == (0, 0), "Swapped piece @ 0,0 has invalid location!" assert swapped_board.cell(4, 5).location == (4, 5), "Swapped piece @ 4,5 has invalid location!" assert isinstance(swapped_board.cell(0, 0), type(original.cell(4, 5))), "Swapped piece @ 0,0 does not match original piece @ 4,5!" assert isinstance(swapped_board.cell(4, 5), type(original.cell(0, 0))), "Swapped piece @ 4,5 does not match original piece @ 0,0!" # swap multiple times should yield expected results original = Board.create_randomized_board(5, 6) swapped_board = Board.copy_board(original) swapped_board.swap(0, 0, 1, 1) swapped_board.swap(1, 1, 2, 2) swapped_board.swap(2, 2, 3, 3) swapped_board.swap(3, 3, 4, 4) assert isinstance(swapped_board.cell(0, 0), type(original.cell(1, 1))), "Swapped piece @ 0,0 does not match original @ 1,1" assert isinstance(swapped_board.cell(1, 1), type(original.cell(2, 2))), "Swapped piece @ 1,1 does not match original @ 2,2" assert isinstance(swapped_board.cell(2, 2), type(original.cell(3, 3))), "Swapped piece @ 2,2 does not match original @ 3,3" assert isinstance(swapped_board.cell(3, 3), type(original.cell(4, 4))), "Swapped piece @ 3,3 does not match original @ 4,4" assert isinstance(swapped_board.cell(4, 4), type(original.cell(0, 0))), "Swapped piece @ 4,4 does not match original @ 0,0"
def run(rows, columns, depth, weights): def update_board_with_matches(board, matches): for piece, clusters in matches: for r, c in clusters: board.update(r, c, type(piece)) b = Board.create_randomized_board(5, 6) # b = Board([Fire, Wood, Water, Dark, Light, Light, # Water, Fire, Water, Light, Heart, Light, # Fire, Water, Dark, Heart, Heart, Wood, # Light, Water, Light, Fire, Wood, Wood, # Dark, Heart, Dark, Light, Heart, Light], 5, 6) m = Board.create_empty_board(rows, columns) update_board_with_matches(m, b.get_matches()) h = GreedyDfs(weights) if False else PrunedBfs(weights) h.diagonals = True start = time.time() moves = h.solve(b, depth) performance = time.time() - start print('---------------------------------------------') print(b) print(m) print('run time : ' + str(performance)) print('best move score : ' + str(moves[0])) print('start : ' + str(moves[1][0])) print('moves : ' + str(moves[1][1:])) print(moves[2]) m = Board.create_empty_board(rows, columns) update_board_with_matches(m, moves[2].get_matches()) print(m)
def test_cell_update(): # updating a board's cell should update that cell only original = Board.create_randomized_board(5, 6) updated_board = Board.copy_board(original).update(3, 4, Jammer) matching = [ updated_board.cell(r, c) for c in range(original.columns) for r in range(original.rows) if isinstance(original.cell(r, c), type(updated_board.cell(r, c))) and original.cell(r, c).location == updated_board.cell(r, c).location ] assert len(matching) == 29, "Updating board did not yield expected matching cells with original board!" assert isinstance(updated_board.cell(3, 4), Jammer), "Piece @ 3,4 was not updated to correct piece!"
def test_copy_board(): # copying a board produces another board with same setup as original original = Board.create_randomized_board(5, 6) copy = Board.copy_board(original) assert original.rows == copy.rows, "Copied board's rows do not match original!" assert original.columns == copy.columns, "Copied board's columns do not match original!" matching = [copy.cell(r, c) for c in range(original.columns) for r in range(original.rows) if isinstance(original.cell(r, c), type(copy.cell(r, c))) and original.cell(r, c).location == copy.cell(r, c).location] assert len(matching) == 30, "Copied board's board does not match original!"
def test_copy_board(): # copying a board produces another board with same setup as original original = Board.create_randomized_board(5, 6) copy = Board.copy_board(original) assert original.rows == copy.rows, "Copied board's rows do not match original!" assert original.columns == copy.columns, "Copied board's columns do not match original!" matching = [ copy.cell(r, c) for c in range(original.columns) for r in range(original.rows) if isinstance(original.cell(r, c), type(copy.cell(r, c))) and original.cell(r, c).location == copy.cell(r, c).location ] assert len(matching) == 30, "Copied board's board does not match original!"
def test_board_setup(): # as long as length of piece list is equal to row x column, the board yields no error and loads from top left down to bottom right b = Board([Fire, Water, Wood, Dark, Light, Heart], 2, 3) assert isinstance(b, Board), "Board valid 2x3, but was not instantiated!" assert isinstance(b.cell(0, 0), Fire), "Unexpected piece @ 0,0 on 2x3 Board!" assert isinstance(b.cell(0, 1), Water), "Unexpected piece @ 0,1 on 2x3 Board!" assert isinstance(b.cell(0, 2), Wood), "Unexpected piece @ 0,2 on 2x3 Board!" assert isinstance(b.cell(1, 0), Dark), "Unexpected piece @ 1,0 on 2x3 Board!" assert isinstance(b.cell(1, 1), Light), "Unexpected piece @ 1,1 on 2x3 Board!" assert isinstance(b.cell(1, 2), Heart), "Unexpected piece @ 1,2 on 2x3 Board!" assert b.rows == 2, "2x3 board has incorrect rows property!" assert b.columns == 3, "2x3 board has incorrect columns property!" assert len(b.board) == 2 and len(b.board[0]) == 3, "2x3 board has incorrect board dimensions!" b = Board([Fire, Water, Wood, Dark, Light, Heart], 3, 2) assert isinstance(b, Board), "Board valid 3x2, but was not instantiated!" assert isinstance(b.cell(0, 0), Fire), "Unexpected piece @ 0,0 on 3x2 Board!" assert isinstance(b.cell(0, 1), Water), "Unexpected piece @ 0,1 on 3x2 Board!" assert isinstance(b.cell(1, 0), Wood), "Unexpected piece @ 1,0 on 3x2 Board!" assert isinstance(b.cell(1, 1), Dark), "Unexpected piece @ 1,1 on 3x2 Board!" assert isinstance(b.cell(2, 0), Light), "Unexpected piece @ 2,0 on 3x2 Board!" assert isinstance(b.cell(2, 1), Heart), "Unexpected piece @ 2,1 on 3x2 Board!" assert b.rows == 3, "3x2 board has incorrect rows property!" assert b.columns == 2, "3x2 board has incorrect columns property!" assert len(b.board) == 3 and len(b.board[0]) == 2, "3x2 board has incorrect board dimensions!"
def test_cell_update(): # updating a board's cell should update that cell only original = Board.create_randomized_board(5, 6) updated_board = Board.copy_board(original).update(3, 4, Jammer) matching = [ updated_board.cell(r, c) for c in range(original.columns) for r in range(original.rows) if isinstance(original.cell(r, c), type(updated_board.cell(r, c))) and original.cell(r, c).location == updated_board.cell(r, c).location ] assert len( matching ) == 29, "Updating board did not yield expected matching cells with original board!" assert isinstance(updated_board.cell(3, 4), Jammer), "Piece @ 3,4 was not updated to correct piece!"
def board(): return Board([ Fire, Unknown, Unknown, Unknown, Dark, Dark, Fire, Water, Unknown, Heart, Unknown, Dark, Fire, Water, Heart, Heart, Unknown, Unknown, Unknown, Water, Unknown, Unknown, Unknown, Unknown, Unknown, Water, Unknown, Light, Light, Light ], 5, 6)
def board_with_0_chain(): return Board([ Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Fire, Unknown, Unknown, Unknown, Unknown, Fire, Unknown, Unknown, Unknown, Unknown, Unknown, Fire, Fire, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown, Unknown ], 5, 6)
def test_empty_board(): # empty board should contain only Unknown pieces b = Board.create_empty_board(5, 6) unknown_pieces = [ b.cell(r, c) for c in range(b.columns) for r in range(b.rows) if isinstance(b.cell(r, c), Unknown) ] assert len( unknown_pieces ) == 30, "Creating empty board does not contain only Unknown pieces!"
def test_randomized_board(): # randomized board should contain only Fire, Water, Wood, Dark, Light, and Heart pieces allowed_pieces = set([Fire, Water, Wood, Dark, Light, Heart]) b = Board.create_randomized_board(5, 6) pieces = [ b.cell(r, c) for c in range(b.columns) for r in range(b.rows) if isinstance(b.cell(r, c), tuple(allowed_pieces)) ] assert len( pieces) == 30, "Creating randomized board contains invalid pieces!"
def test_cell_swap(board_with_1_chain): # swapping a board's cell with another should only affect the source and target cells original = board_with_1_chain swapped_board = Board.copy_board(original) # swap corners where the two pieces are different swapped_board.swap(0, 0, 4, 5) matching = [ swapped_board.cell(r, c) for c in range(original.columns) for r in range(original.rows) if isinstance(original.cell(r, c), type(swapped_board.cell(r, c))) and original.cell(r, c).location == swapped_board.cell(r, c).location ] assert len( matching ) == 28, "Swapping upper left corner with lower right corner of board yielded match counts!" assert swapped_board.cell( 0, 0).location == (0, 0), "Swapped piece @ 0,0 has invalid location!" assert swapped_board.cell( 4, 5).location == (4, 5), "Swapped piece @ 4,5 has invalid location!" assert isinstance(swapped_board.cell(0, 0), type(original.cell( 4, 5))), "Swapped piece @ 0,0 does not match original piece @ 4,5!" assert isinstance(swapped_board.cell(4, 5), type(original.cell( 0, 0))), "Swapped piece @ 4,5 does not match original piece @ 0,0!" # swap multiple times should yield expected results original = Board.create_randomized_board(5, 6) swapped_board = Board.copy_board(original) swapped_board.swap(0, 0, 1, 1) swapped_board.swap(1, 1, 2, 2) swapped_board.swap(2, 2, 3, 3) swapped_board.swap(3, 3, 4, 4) assert isinstance(swapped_board.cell(0, 0), type(original.cell( 1, 1))), "Swapped piece @ 0,0 does not match original @ 1,1" assert isinstance(swapped_board.cell(1, 1), type(original.cell( 2, 2))), "Swapped piece @ 1,1 does not match original @ 2,2" assert isinstance(swapped_board.cell(2, 2), type(original.cell( 3, 3))), "Swapped piece @ 2,2 does not match original @ 3,3" assert isinstance(swapped_board.cell(3, 3), type(original.cell( 4, 4))), "Swapped piece @ 3,3 does not match original @ 4,4" assert isinstance(swapped_board.cell(4, 4), type(original.cell( 0, 0))), "Swapped piece @ 4,4 does not match original @ 0,0"
def test_invalid_board(): # length of list must be equal to row x column for the board to be valid with pytest.raises(Exception) as exec_info: Board([Fire, Water, Wood, Dark, Light, Heart, Poison, Jammer, Unknown], 10, 10) assert 'Given pieces do not fit specified board dimensions!' in str(exec_info.value), \ "Board's pieces list was smaller than dimensions, but did not fail initialization!" with pytest.raises(Exception) as exec_info: Board([Fire, Water, Wood, Dark, Light, Heart, Poison, Jammer, Unknown], 2, 2) assert 'Given pieces do not fit specified board dimensions!' in str(exec_info.value), \ "Board's pieces list was bigger than dimensions, but did not fail initialization!" with pytest.raises(Exception) as exec_info: Board([Fire, Water, Wood, Dark, Light, Heart, Poison, Jammer, Unknown], 0, 3) assert 'Given pieces do not fit specified board dimensions!' in str( exec_info.value ), "Board's row was invalid, but did not fail initialization!"
def _step(self, solutions, depth): if depth == 0: return solutions else: next_solutions = [] for score, moves, board, row, column in solutions: for delta_r, delta_c in self._swaps(board, row, column): swapped_board = Board.copy_board(board).swap(row, column, row + delta_r, column + delta_c) next_solutions.append((score + self._score(swapped_board), (moves + ((delta_r, delta_c),)), swapped_board, row + delta_r, column + delta_c)) # prune solutions down before recursing to next depth return self._step(self._prune(next_solutions), depth - 1)
def _step(self, score, moves, board, row, column, depth): if depth == 0: return (score, moves, board) else: # get possible swaps from current row, column position swaps = self._swaps(board, row, column) solutions = [] for delta_r, delta_c in swaps: swapped_board = Board.copy_board(board).swap(row, column, row + delta_r, column + delta_c) solutions.append((self._score(swapped_board), swapped_board, (delta_r, delta_c))) # calculate score on all possible swaps and order them from highest to lowest. recurse into the highest one best = max(solutions, key=lambda x: x[0]) return self._step(score + best[0], (moves + (best[2],)), best[1], row + best[2][0], column + best[2][1], depth - 1)
def _step(self, score, moves, board, row, column, depth): if depth == 0: return (score, moves, board) else: # get possible swaps from current row, column position swaps = self._swaps(board, row, column) solutions = [] for delta_r, delta_c in swaps: swapped_board = Board.copy_board(board).swap(row, column, row + delta_r, column + delta_c) if self._remember(swapped_board): # only add move to solutions if it is a board layout that has not been seen before solutions.append((self._score(swapped_board), swapped_board, (delta_r, delta_c))) if solutions: # calculate score on all possible swaps and order them from highest to lowest. recurse into the highest one best = max(solutions, key=lambda x: x[0]) return self._step(score + best[0], (moves + (best[2],)), best[1], row + best[2][0], column + best[2][1], depth - 1) else: return (score, moves, board)
def _step(self, solutions, depth): if depth == 0: return solutions else: next_solutions = [] for score, moves, board, row, column in solutions: for delta_r, delta_c in self._swaps(board, row, column): swapped_board = Board.copy_board(board).swap( row, column, row + delta_r, column + delta_c) if self._remember(swapped_board): # only add move to solutions if it is a board layout that has not been seen before next_solutions.append( (score + self._score(swapped_board), (moves + ((delta_r, delta_c), )), swapped_board, row + delta_r, column + delta_c)) if next_solutions: # prune solutions down before recursing to next depth return self._step(self._prune(next_solutions), depth - 1) else: return solutions
Heart.symbol: 1.0, Poison.symbol: 0.5, Jammer.symbol: 0.5, Unknown.symbol: 0.0 } #board = Board.create_randomized_board(5, 6) piece_list = [ Fire, Wood, Water, Dark, Light, Heart, Fire, Water, Dark, Light, Heart, Fire, Fire, Water, Dark, Heart, Heart, Wood, Light, Water, Light, Fire, Wood, Wood, Dark, Water, Dark, Light, Light, Light ] number_of_rows = 5 number_of_columns = 6 board = Board(piece_list, number_of_rows, number_of_columns) matches = board.get_matches() print(board) print(matches) solver1 = GreedyDfs(weights) solution = solver1.solve(board, 100) print(solution) solver2 = PrunedBfs(weights) solution = solver2.solve(board, 100) print(solution)
def test_randomized_board(): # randomized board should contain only Fire, Water, Wood, Dark, Light, and Heart pieces allowed_pieces = set([Fire, Water, Wood, Dark, Light, Heart]) b = Board.create_randomized_board(5, 6) pieces = [b.cell(r, c) for c in range(b.columns) for r in range(b.rows) if isinstance(b.cell(r, c), tuple(allowed_pieces))] assert len(pieces) == 30, "Creating randomized board contains invalid pieces!"
def test_empty_board(): # empty board should contain only Unknown pieces b = Board.create_empty_board(5, 6) unknown_pieces = [b.cell(r, c) for c in range(b.columns) for r in range(b.rows) if isinstance(b.cell(r, c), Unknown)] assert len(unknown_pieces) == 30, "Creating empty board does not contain only Unknown pieces!"
def board_with_1_chain(): return Board([ Fire, Wood, Water, Dark, Light, Poison, Water, Water, Water, Light, Heart, Jammer, Fire, Water, Dark, Heart, Heart, Wood, Light, Water, Light, Fire, Wood, Wood, Dark, Heart, Dark, Light, Heart, Light ], 5, 6)
from pazudorasolver.heuristics.greedy_dfs import GreedyDfs from pazudorasolver.heuristics.pruned_bfs import PrunedBfs weights = { Fire.symbol: 1.0, Wood.symbol: 1.0, Water.symbol: 1.0, Dark.symbol: 1.0, Light.symbol: 1.0, Heart.symbol: 1.0, Poison.symbol: 0.5, Jammer.symbol: 0.5, Unknown.symbol: 0.0 } board = Board.create_randomized_board(5, 6) matches = board.get_matches() print(board) print(matches) # try GreedyDfs heuristic solver1 = GreedyDfs(weights) solution = solver1.solve(board, 50) print(solution) # try PrunedBfs heuristic solver2 = PrunedBfs(weights) solution = solver2.solve(board, 50)
def test_board_setup(): # as long as length of piece list is equal to row x column, the board yields no error and loads from top left down to bottom right b = Board([Fire, Water, Wood, Dark, Light, Heart], 2, 3) assert isinstance(b, Board), "Board valid 2x3, but was not instantiated!" assert isinstance(b.cell(0, 0), Fire), "Unexpected piece @ 0,0 on 2x3 Board!" assert isinstance(b.cell(0, 1), Water), "Unexpected piece @ 0,1 on 2x3 Board!" assert isinstance(b.cell(0, 2), Wood), "Unexpected piece @ 0,2 on 2x3 Board!" assert isinstance(b.cell(1, 0), Dark), "Unexpected piece @ 1,0 on 2x3 Board!" assert isinstance(b.cell(1, 1), Light), "Unexpected piece @ 1,1 on 2x3 Board!" assert isinstance(b.cell(1, 2), Heart), "Unexpected piece @ 1,2 on 2x3 Board!" assert b.rows == 2, "2x3 board has incorrect rows property!" assert b.columns == 3, "2x3 board has incorrect columns property!" assert len(b.board) == 2 and len( b.board[0]) == 3, "2x3 board has incorrect board dimensions!" b = Board([Fire, Water, Wood, Dark, Light, Heart], 3, 2) assert isinstance(b, Board), "Board valid 3x2, but was not instantiated!" assert isinstance(b.cell(0, 0), Fire), "Unexpected piece @ 0,0 on 3x2 Board!" assert isinstance(b.cell(0, 1), Water), "Unexpected piece @ 0,1 on 3x2 Board!" assert isinstance(b.cell(1, 0), Wood), "Unexpected piece @ 1,0 on 3x2 Board!" assert isinstance(b.cell(1, 1), Dark), "Unexpected piece @ 1,1 on 3x2 Board!" assert isinstance(b.cell(2, 0), Light), "Unexpected piece @ 2,0 on 3x2 Board!" assert isinstance(b.cell(2, 1), Heart), "Unexpected piece @ 2,1 on 3x2 Board!" assert b.rows == 3, "3x2 board has incorrect rows property!" assert b.columns == 2, "3x2 board has incorrect columns property!" assert len(b.board) == 3 and len( b.board[0]) == 2, "3x2 board has incorrect board dimensions!"
def board_with_3_chain(): return Board([ Fire, Wood, Water, Dark, Light, Heart, Fire, Water, Dark, Light, Heart, Fire, Fire, Water, Dark, Heart, Heart, Wood, Light, Water, Light, Fire, Wood, Wood, Dark, Water, Dark, Light, Light, Light ], 5, 6)