def test_clear_letters(self): grid = Grid(squares) grid = optimize._clear_letters((0, 2), (0, 2), grid) self.assertEqual(grid.get_square((0, 0)), '.') self.assertEqual(grid.get_square((0, 1)), '.') self.assertEqual(grid.get_square((1, 0)), '') self.assertEqual(grid.get_square((1, 1)), '')
def test_get_all_words(self): squares = [ ['.', '', '', '', ''], ['', '', '', '', ''], ['', '', '.', '', ''], ['', '', '', '', ''], ['', '', '', '', '.'] ] grid = Grid(squares, 5) words = grid.get_all_words() across = [w[0] for w in words if w[1] == Mode.ACROSS] self.assertEqual(across, [(0, 1), (1, 0), (2, 0), (2, 3), (3, 0), (4, 0)]) down = [w[0] for w in words if w[1] == Mode.DOWN] self.assertEqual(down, [(0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (3, 2)])
def test_finish_two_step_puzzle(self): squares = [ ['.', '.', ' ', ' ', ' '], ['B', 'E', 'E', ' ', 'H'], ['A', 'S', 'A', 'H', 'I'], ['T', 'A', 'C', ' ', 'S'], ['H', 'U', 'H', '.', '.'] ] grid = Grid(squares) result, score = self.generator.optimize(grid) self.assertEqual(score, 475)
def test_get_next_target(self): squares = [['X', 'X', 'X'], ['', 'X', ''], ['X', '', '']] grid = Grid(squares) info = SearchInfo(unfilled_words=[((1, 0), Mode.ACROSS), ((2, 0), Mode.ACROSS), ((0, 0), Mode.DOWN), ((0, 1), Mode.DOWN), ((0, 2), Mode.DOWN)], used_words=[]) target, direction = self.generator.get_next_target(grid, info) self.assertEqual(target, (0, 0)) self.assertEqual(direction, Mode.DOWN)
def test_copy(self): grid = Grid(squares) copy = grid.copy() grid.set_square((2, 2), 'X') self.assertEqual(grid.get_square((2, 2)), 'X') self.assertEqual(copy.get_square((2, 2)), '')
def __init__(self, squares=None, filename=None, size=15, dictionary_path="dictionaries/"): if squares: self.grid = Grid(squares, size) elif filename and path.exists(filename): squares = storage.load(filename) self.grid = Grid(squares) else: self.grid = Grid(size=size) self.size = self.grid.size self.focus = (0, 0) self.highlight = [] self.mode = Mode.ACROSS self.dictionary = Dictionary(dictionary_path)
class Model: """ A class that holds all of the UI state for a crossword puzzle. """ def __init__(self, squares=None, filename=None, size=15, dictionary_path="dictionaries/"): if squares: self.grid = Grid(squares, size) elif filename and path.exists(filename): squares = storage.load(filename) self.grid = Grid(squares) else: self.grid = Grid(size=size) self.size = self.grid.size self.focus = (0, 0) self.highlight = [] self.mode = Mode.ACROSS self.dictionary = Dictionary(dictionary_path) def toggle_orientation(self): self.mode = self.mode.opposite() self.update_highlighted_squares() def update_focus(self, row, col): self.focus = (row, col) self.update_highlighted_squares() def save(self, filename): storage.save(self.grid.squares, filename) def update_square(self, row, col, text): # maintain block symmetry if text == BLOCK: # add corresponding block self.grid.set_square((self.size - 1 - row, self.size - 1 - col), BLOCK) elif self.grid.get_square((row, col)) == BLOCK and text != BLOCK: # remove corresponding block if this square used to be a block self.grid.set_square((self.size - 1 - row, self.size - 1 - col), '') self.grid.set_square((row, col), text) self.get_next_focus(text) self.update_highlighted_squares() def get_square(self, row, col): text = self.grid.get_square((row, col)) background = Background.WHITE if text == BLOCK: background = Background.BLACK elif (row, col) in self.highlight: background = Background.YELLOW focused = (row, col) == self.focus return Square(text, background, focused) def update_highlighted_squares(self): self.highlight = self.grid.get_word_squares(self.focus, self.mode) def get_next_focus(self, text): """ Get the coordinates of the square that should be focused after the given square """ if text == '': # text was deleted, go backwards if self.mode is Mode.ACROSS: self.move_left() else: self.move_up() else: # text was added if self.mode is Mode.ACROSS: self.move_right() else: self.move_down() def move_up(self): self.focus = (max(0, self.focus[0] - 1), self.focus[1]) def move_down(self): self.focus = (min(self.size - 1, self.focus[0] + 1), self.focus[1]) def move_left(self): self.focus = (self.focus[0], max(0, self.focus[1] - 1)) def move_right(self): self.focus = (self.focus[0], min(self.size - 1, self.focus[1] + 1)) def get_suggestions(self): # returns suggestions for the focused square # prioritizes words that use a letter compatible with crossing words # (across, down), each is a list of tuples (word, score) if self.grid.get_square(self.focus) == BLOCK: return [], [] across = self._get_suggestions(self.focus, Mode.ACROSS) down = self._get_suggestions(self.focus, Mode.DOWN) return across, down def _get_suggestions(self, square, mode): word = self.grid.get_word(square, mode) squares = self.grid.get_word_squares(square, mode) # copy to avoid modifying cached lists words = self.dictionary.search(word).copy() compatible_words = words.copy() # loop through empty letter index and remove words that don't fit crossing words for i, s in enumerate(squares): if not self.grid.is_empty(s): # skip squares that are already filled in continue cross_index = self.grid.get_word_squares(s, mode.opposite()).index(s) cross_word = self.grid.get_word(s, mode.opposite()) available_letters = self.dictionary.get_allowed_letters(cross_word, cross_index) compatible_words = [w for w in compatible_words if w[0][i] in available_letters] # add bonus to compatible words for i, w in enumerate(words): if w in compatible_words: words[i] = w[0], str(int(w[1]) + 100) words.sort(key=lambda w: int(w[1]), reverse=True) return words def fill(self): """ Fill the blank squares using a Generator """ filled_grid, _ = optimize(self.grid, self.dictionary) self.grid = filled_grid def print(self): self.grid.print()
def test_get_word_squares(self, square, mode, expected): grid = Grid(squares, 5) actual = grid.get_word_squares(square, mode) self.assertEqual(actual, expected)
def test_is_complete(self): grid = Grid(squares) self.assertFalse(grid.is_complete()) grid.set_square((2, 2), 'A') self.assertTrue(grid.is_complete())
def test_is_block(self): grid = Grid(squares, 5) self.assertTrue(grid.is_block((2, 1))) self.assertFalse(grid.is_block((3, 3)))
def test_is_empty(self): grid = Grid(squares, 5) self.assertTrue(grid.is_empty((2, 2))) self.assertFalse(grid.is_empty((0, 2)))
def test_get_square(self, square, expected): grid = Grid(squares, 5) self.assertEqual(grid.get_square(square), expected)
def test_create_empty_grid(self): grid = Grid(None, 2) self.assertEqual(grid.squares, [['', ''], ['', '']]) self.assertEqual(grid.size, 2)
def test_raise_error_if_not_square(self): with self.assertRaises(AssertionError): Grid([[''], ['', '']], 3)
def test_get_possible_words_down(self): squares = [['', '', '', ''], ['', '', '', ''], ['', 'O', 'T', 'E'], ['', '', '', '']] grid = Grid(squares) words = self.generator.get_possible_words(grid, (0, 0), Mode.DOWN) self.assertEqual(words, [("bind", 50)])
def test_set_square(self, square, text, expected): grid = Grid(squares, 5) grid.set_square(square, text) self.assertEqual(grid.get_square(square), expected)
def test_set_word(self): grid = Grid() word = "ABCDEFGHIJKLMNO" self.generator.set_word(grid, (1, 2), Mode.ACROSS, word) self.assertEqual(grid.get_word((1, 2), Mode.ACROSS), word)