def test_add_black_cell(self): grid = Grid(9) grid.add_black_cell(1, 5) grid.add_black_cell(4, 9) expected = [(1, 5), (4, 9), (6, 1), (9, 5)] actual = grid.get_black_cells() self.assertEqual(expected, actual)
def test_add_black(self): n = 5 grid = Grid(n) black_cells = [ (1, 1), (1, 3), (2, 3), (2, 4), (3, 1), (3, 2), ] for (r, c) in black_cells: grid.add_black_cell(r, c) puzzle = Puzzle(grid) expected_cells = [ ["*", " ", "*", " ", " "], [" ", " ", "*", "*", " "], ["*", "*", " ", "*", "*"], [" ", "*", "*", " ", " "], [" ", " ", "*", " ", "*"], ] for r in range(1, n + 1): for c in range(1, n + 1): expected = expected_cells[r - 1][c - 1] actual = puzzle.get_cell(r, c) self.assertEqual(expected, actual, f'Mismatch at ({r},{c})')
def create_atlantic_puzzle(): """ Creates a puzzle from the Atlantic puzzle of May 15, 2020 +-----------------+ |D|A|B|*|*|E|F|T|S| |S|L|I|M|*|R|I|O|T| |L|O|C|A|V|O|R|E|S| |R|E|U|N|I|T|E|D|*| |*|*|R|A|P|I|D|*|*| |*|R|I|C|E|C|A|K|E| |C|O|O|L|R|A|N|C|H| |C|L|U|E|*|C|A|L|X| |R|O|S|S|*|*|E|R|E| +-----------------+ """ n = 9 grid = Grid(n) for (r, c) in [ (1, 4), (1, 5), (2, 5), (4, 9), (5, 8), (5, 9), ]: grid.add_black_cell(r, c) puzzle = Puzzle(grid) puzzle.title = 'My Atlantic Theme' return puzzle
def test_add_undo(self): grid = Grid(5) grid.add_black_cell(1, 2) self.assertEqual(True, grid.is_black_cell(1, 2)) self.assertEqual(True, grid.is_black_cell(5, 4)) self.assertEqual([(1, 2)], grid.undo_stack) self.assertEqual([], grid.redo_stack) grid.undo() self.assertEqual(False, grid.is_black_cell(1, 2)) self.assertEqual(False, grid.is_black_cell(5, 4)) self.assertEqual([], grid.undo_stack) self.assertEqual([(1, 2)], grid.redo_stack)
def from_json(jsonstr): image = json.loads(jsonstr) # Create a puzzle of the specified size n = image['n'] grid = Grid(n) # Initialize the black cells black_cells = image['black_cells'] for black_cell in black_cells: grid.add_black_cell(*black_cell) # Create the puzzle puzzle = Puzzle(grid) title = image.get('title', None) puzzle._title = title # Can't use the undo/redo here yet # Reload the "ACROSS" words awlist = image['across_words'] for aw in awlist: seq = aw['seq'] text = aw['text'] clue = aw['clue'] word = puzzle.get_across_word(seq) word.set_text(text) # TODO: Can't do this - undo/redo word.set_clue(clue) # TODO: Can't do this - undo/redo # Reload the "DOWN" words dwlist = image['down_words'] for dw in dwlist: seq = dw['seq'] text = dw['text'] clue = dw['clue'] word = puzzle.get_down_word(seq) word.set_text(text) # TODO: Can't do this - undo/redo word.set_clue(clue) # TODO: Can't do this - undo/redo # Reload the undo/redo stacks puzzle.undo_stack = image.get('undo_stack', []) puzzle.redo_stack = image.get('redo_stack', []) # Done return puzzle
def create_puzzle(): """ Creates sample puzzle as: +---------+ | | |*| | | | | |*|*| | |*|*| |*|*| | |*|*| | | | | |*| | | +---------+ """ n = 5 grid = Grid(n) for (r, c) in [ (1, 3), (2, 3), (2, 4), (3, 1), (3, 2), ]: grid.add_black_cell(r, c) return Puzzle(grid)
def create_nyt_puzzle(): """ Creates a puzzle from a real New York Times crossword of March 15, 2020 +-----------------------------------------+ |A|B|B|A|*| | | |*| | | | | |*| | | | | | | | | | | |*| | | |*| | | | | |*| | | | | | | | | | | | | | | | | | | | | |*| | | | | | | | | | | | |*| | | | | |*| | | |*| | | | | | | | | |*| | | | | |*|*| | | | | |*|*| | | | |*|*|*| | | | | | | | | | | | | | | | | | | | | | | | | |*|*| | | | | | |*| | | | | | | | | | | |*| | | |*| | | |*|*| | | | | | | | | | | | | | | | | | | | | | | | | | |*|*|*| | | | |*| | | | | |*| | | | | | |*| | | | | | | | | | |*| | | | |*| | | | |*| | | | | | | | | | |*| | | | | | |*| | | | | |*| | | | |*|*|*| | | | | | | | | | | | | | | | | | | | | | | | | | |*|*| | | |*| | | |*| | | | | | | | | | | |*| | | | | | |*|*| | | | | | | | | | | | | | | | | | | | | | | | | |*|*|*| | | | |*|*| | | | | |*|*| | | | | |*| | | | | | | | | |*| | | |*| | | | | |*| | | | | | | | | | | | |*| | | | | | | | | | | | | | | | | | | | | |*| | | | | |*| | | |*| | | | | | | | | | | |*| | | | | |*| | | |*| | | | | +-----------------------------------------+ """ n = 21 grid = Grid(n) for (r, c) in [(1, 5), (1, 9), (1, 15), (2, 5), (2, 9), (2, 15), (3, 15), (4, 6), (4, 12), (4, 16), (5, 4), (5, 10), (5, 11), (5, 17), (5, 18), (6, 1), (6, 2), (6, 3), (7, 7), (7, 8), (7, 15), (8, 5), (8, 9), (8, 13), (8, 14), (9, 19), (9, 20), (9, 21), (10, 4), (10, 10), (11, 6), (11, 11), (12, 5), (13, 1), (13, 2), (13, 3), (14, 8), (15, 7), (17, 4), (17, 5)]: grid.add_black_cell(r, c) return Puzzle(grid)
def test_to_json(self): grid = Grid(9) grid.add_black_cell(1, 5) grid.add_black_cell(4, 9) jsonstr = grid.to_json() self.assertIsNotNone(jsonstr)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) grid = Grid(9) for r, c in [(1, 4), (1, 5), (2, 5), (5, 1), (5, 2), (6, 1)]: grid.add_black_cell(r, c) self.nclist = grid.get_numbered_cells()
def create_nyt_daily(): """ from https://www.nytimes.com/crosswords/game/daily/2016/09/20 """ grid = Grid(15) for r, c in [(1, 5), (1, 11), (2, 5), (2, 11), (3, 5), (3, 11), (4, 14), (4, 15), (5, 7), (5, 12), (6, 1), (6, 2), (6, 3), (6, 8), (6, 9), (7, 4), (8, 5), (8, 6), (8, 10), (8, 11), (11, 4)]: grid.add_black_cell(r, c) puzzle = Puzzle(grid) for seq, text in [(1, "ACTS"), (5, "PLASM"), (10, "EDGY"), (14, "SHIV"), (15, "RUCHE"), (16, "VALE"), (17, "NINE"), (18, "USUAL"), (19, "IRON"), (20, "ENGLISHTRIFLE"), (23, "RASTAS"), (24, "EDNA"), (25, "WAS"), (28, "EMIT"), (30, "DIGEST"), (32, "MAY"), (35, "BAKEDALASKA"), (38, "ALOT"), (40, "ONO"), (41, "BAER"), (42, "PLUMPUDDING"), (47, "YDS"), (48, "LESSON"), (49, "TIOS"), (51, "EYE"), (52, "OHMS"), (55, "CLUMSY"), (59, "NOPIECEOFCAKE"), (62, "UNDO"), (64, "NAOMI"), (65, "KRIS"), (66, "SIMP"), (67, "GLOMS"), (68, "EIRE"), (69, "EXES"), (70, "ESTEE"), (71, "RATS")]: puzzle.get_across_word(seq).set_text(text) for seq, clue in [ (1, "___ of the Apostles"), (5, "Ending with neo- or proto-"), (10, "Pushing conventional limits"), (14, "Blade in the pen"), (15, "Strip of fabric used for trimming"), (16, "Low ground, poetically"), (17, "Rock's ___ Inch Nails"), (18, "Habitual customer's order, with \"the\""), (19, "Clothes presser"), (20, "Layers of sherry-soaked torte, homemade custard and fruit served chilled in a giant stem glass" ), (23, "Dreadlocked ones, informally"), (24, "Comical \"Dame\""), (25, "\"Kilroy ___ here\""), (28, "Give off, as vibes"), (30, "Summary"), (32, "___-December romance"), (35, "Ice cream and sponge topped with meringue and placed in a very hot oven for a few minutes" ), (38, "Oodles"), (40, "Singer with the site imaginepeace.com"), (41, "Boxer Max"), (42, "Steamed-for-hours, aged-for-months concoction of treacle, brandy, fruit and spices, set afire and served at Christmas" ), (47, "Fabric purchase: Abbr."), (48, "Teacher's plan"), (49, "Uncles, in Acapulco"), (51, "___ contact"), (52, "Units of resistance"), (55, "Ham-handed"), (59, "What a chef might call each dessert featured in this puzzle, literally or figuratively" ), (62, "Command-Z command"), (64, "Actress Watts"), (65, "Kardashian matriarch"), (66, "Fool"), (67, "Latches (onto)"), (68, "Land of Blarney"), (69, "Ones who are splitsville"), (70, "Lauder of cosmetics"), (71, "\"Phooey!\""), ]: puzzle.get_across_word(seq).set_clue(clue) for seq, clue in [ (1, "Ed of \"Up\""), (2, "Set traditionally handed down to an eldest daughter"), (3, "Tiny bell sounds"), (4, "Willowy"), (5, "German kingdom of old"), (6, "Growing luxuriantly"), (7, "Severe and short, as an illness"), (8, "Glass fragment"), (9, "Gates of philanthropy"), (10, "Voldemort-like"), (11, "\"Hesitating to mention it, but...\""), (12, "Mop & ___"), (13, "Itch"), (21, "da-DAH"), (22, "Pass's opposite"), (26, "\"___ and answered\" (courtroom objection)"), (27, "Constellation units"), (29, "Walloped to win the bout, in brief"), (31, "Chew the fat"), (32, "Sugar ___"), (33, "Locale for urban trash cans"), (34, "Sam Cooke's first #1 hit"), (36, "Come to a close"), (37, "\"I dare you!\""), (39, "Designs with ® symbols: Abbr."), (43, "Lowdown, in slang"), (44, "Drive mad"), (45, "Salade ___"), (46, "Club game"), (50, "Lollipop"), (53, "\"Square\" things, ideally"), (54, "\"Git!\""), (56, "\"West Side Story\" seamstress"), (57, "Mini, e.g."), (58, "Positive R.S.V.P.s"), (60, "Error report?"), (61, "J.Lo's daughter with a palindromic name"), (62, "Manipulate"), (63, "Kill, as an idea"), ]: puzzle.get_down_word(seq).set_clue(clue) return puzzle
def create_puzzle(self, xmlstr): ns = { 'cc': 'http://crossword.info/xml/crossword-compiler', 'rp': 'http://crossword.info/xml/rectangular-puzzle' } root = ET.fromstring(xmlstr) elem_rp = root.find('rp:rectangular-puzzle', ns) elem_crossword = elem_rp.find('rp:crossword', ns) elem_grid = elem_crossword.find('rp:grid', ns) # Grid size n = int(elem_grid.get('height')) grid = Grid(n) # Black cells for elem_cell in elem_grid.findall('rp:cell', ns): celltype = elem_cell.get('type') if celltype == 'block': r = int(elem_cell.get('y')) c = int(elem_cell.get('x')) grid.add_black_cell(r, c) # Title elem_title = elem_rp.find('rp:metadata/rp:title', ns) title = elem_title.text if not title: title = None # Puzzle puzzle = Puzzle(grid, title) # Add the cells for elem_cell in elem_grid.findall('rp:cell', ns): r = int(elem_cell.get('y')) c = int(elem_cell.get('x')) if puzzle.is_black_cell(r, c): continue letter = elem_cell.get('solution') if letter != ' ': puzzle.set_cell(r, c, letter) # Map the word ID to numbered cells wordmap = {} for elem_word in elem_crossword.findall('rp:word', ns): wordid = elem_word.get('id') r = int(elem_word.get('y').split('-')[0]) c = int(elem_word.get('x').split('-')[0]) nc = puzzle.get_numbered_cell(r, c) wordmap[wordid] = nc # Add the clues for elem_clues in elem_crossword.findall('rp:clues', ns): elem_b = elem_clues.find('rp:title/rp:b', ns) direction = elem_b.text[0] # A or D for elem_clue in elem_clues.findall('rp:clue', ns): wordid = elem_clue.get('word') nc = wordmap[wordid] clue = elem_clue.text puzzle.set_clue(nc.seq, direction, clue) return puzzle