def grid_screen(): """ Renders the grid screen """ # Get the existing grid from the session grid = Grid.from_json(session['grid']) gridname = session.get('gridname', None) # Create the SVG svg = GridToSVG(grid) boxsize = svg.boxsize svgstr = svg.generate_xml() # Set the state to editing grid session['uistate'] = UIState.EDITING_GRID enabled = session['uistate'].get_enabled() enabled["grid_undo"] = len(grid.undo_stack) > 0 enabled["grid_redo"] = len(grid.redo_stack) > 0 enabled["grid_delete"] = gridname is not None # Show the grid.html screen return render_template('grid.html', enabled=enabled, n=grid.n, gridname=gridname, boxsize=boxsize, svgstr=svgstr)
def puzzle_new(): """ Creates a new puzzle and redirects to puzzle screen """ # Get the chosen grid name from the query parameters gridname = request.args.get('gridname') # Open the corresponding file and read its contents as json # and recreate the grid from it userid = 1 # TODO replace hard-coded user ID query = DBGrid.query.filter_by(userid=userid, gridname=gridname) jsonstr = query.first().jsonstr grid = Grid.from_json(jsonstr) # Now pass this grid to the Puzzle() constructor puzzle = Puzzle(grid) # Save puzzle in the session jsonstr = puzzle.to_json() session['puzzle'] = jsonstr session['puzzle.initial.sha'] = sha256(jsonstr) # Remove any leftover puzzle name in the session session.pop('puzzlename', None) return redirect(url_for('uipuzzle.puzzle_screen'))
def get_bad_grid(): jsonstr = """ { "n": 7, "black_cells": [ [ 1, 3 ], [ 2, 3 ], [ 3, 3 ], [ 3, 4 ], [ 3, 5 ], [ 4, 2 ], [ 4, 6 ], [ 5, 3 ], [ 5, 4 ], [ 5, 5 ], [ 6, 5 ], [ 7, 5 ] ], "numbered_cells": [ { "seq": 1, "r": 1, "c": 1, "a": 2, "d": 7 }, { "seq": 2, "r": 1, "c": 2, "a": 0, "d": 3 }, { "seq": 3, "r": 1, "c": 4, "a": 4, "d": 2 }, { "seq": 4, "r": 1, "c": 5, "a": 0, "d": 2 }, { "seq": 5, "r": 1, "c": 6, "a": 0, "d": 3 }, { "seq": 6, "r": 1, "c": 7, "a": 0, "d": 7 }, { "seq": 7, "r": 2, "c": 1, "a": 2, "d": 0 }, { "seq": 8, "r": 2, "c": 4, "a": 4, "d": 0 }, { "seq": 9, "r": 3, "c": 1, "a": 2, "d": 0 }, { "seq": 10, "r": 3, "c": 6, "a": 2, "d": 0 }, { "seq": 11, "r": 4, "c": 3, "a": 3, "d": 0 }, { "seq": 12, "r": 5, "c": 1, "a": 2, "d": 0 }, { "seq": 13, "r": 5, "c": 2, "a": 0, "d": 3 }, { "seq": 14, "r": 5, "c": 6, "a": 2, "d": 3 }, { "seq": 15, "r": 6, "c": 1, "a": 4, "d": 0 }, { "seq": 16, "r": 6, "c": 3, "a": 0, "d": 2 }, { "seq": 17, "r": 6, "c": 4, "a": 0, "d": 2 }, { "seq": 18, "r": 6, "c": 6, "a": 2, "d": 0 }, { "seq": 19, "r": 7, "c": 1, "a": 4, "d": 0 }, { "seq": 20, "r": 7, "c": 6, "a": 2, "d": 0 } ] } """ grid = Grid.from_json(jsonstr) return grid
def grid_redo(): """ Redoes the last grid action then redirects to grid screen """ jsonstr = session.get('grid', None) grid = Grid.from_json(jsonstr) grid.redo() jsonstr = grid.to_json() session['grid'] = jsonstr return redirect(url_for('uigrid.grid_screen'))
def grid_save_common(gridname): """ Common method used by both grid_save and grid_save_as """ # Recreate the grid from the JSON in the session # and validate it jsonstr = session.get('grid', None) grid = Grid.from_json(jsonstr) ok, messages = grid.validate() if not ok: flash("Grid not saved") for message_type in messages: message_list = messages[message_type] if len(message_list) > 0: flash(f"*** {message_type} ***") for message in message_list: flash(" " + message) else: # Save the file userid = 1 # TODO Replace hard coded user id query = DBGrid.query.filter_by(userid=userid, gridname=gridname) if not query.all(): # No grid in the database. This is an insert logging.debug(f"Inserting grid '{gridname}' into grids table") created = modified = datetime.now().isoformat() newgrid = DBGrid(userid=userid, gridname=gridname, created=created, modified=modified, jsonstr=jsonstr) db.session.add(newgrid) db.session.commit() else: # There is a grid. This is an update logging.debug(f"Updating grid '{gridname}' in grids table") oldgrid = query.first() oldgrid.modified = datetime.now().isoformat() oldgrid.jsonstr = jsonstr db.session.commit() # Send message about save flash(f"Grid saved as {gridname}") # Store the sha256 of the saved version of the grid # in the session as 'grid.initial.sha' so that we can detect # whether it has been changed since it was last saved session['grid.initial.sha'] = sha256(jsonstr) # Show the grid screen return redirect(url_for('uigrid.grid_screen'))
def grid_rotate(): """ Rotates the grid 90 degrees left then returns the new SVG """ # Rotate the grid jsonstr = session.get('grid', None) grid = Grid.from_json(jsonstr) grid.rotate() # Save the updated grid in the session session['grid'] = grid.to_json() # Send the new SVG data to the client svg = GridToSVG(grid) svgstr = svg.generate_xml() response = make_response(svgstr, HTTPStatus.OK) return response
def grid_statistics(): """ Return the grid statistics in a JSON string """ # Get the grid from the session grid = Grid.from_json(session['grid']) gridname = session.get('gridname', None) stats = grid.get_statistics() enabled = {} svgstr = GridToSVG(grid).generate_xml() # Render with grid statistics template return render_template("grid-statistics.html", enabled=enabled, gridname=gridname, svgstr=svgstr, stats=stats)
def grid_new_from_puzzle(): """ Creates a new grid from the specified puzzle """ puzzlename = request.args.get('puzzlename') userid = 1 # TODO replace hard-coded user ID query = DBPuzzle.query.filter_by(userid=userid, puzzlename=puzzlename) jsonstr = query.first().jsonstr grid = Grid.from_json(jsonstr) grid.undo_stack = [] grid.redo_stack = [] jsonstr = grid.to_json() # Save grid in the session session['grid'] = jsonstr session['grid.initial.sha'] = sha256(jsonstr) # Remove any leftover grid name in the session session.pop('gridname', None) return redirect(url_for('uigrid.grid_screen'))
def grid_preview(): """ Creates a grid preview and returns it to ??? """ userid = 1 # TODO Replace hard coded user id # Get the chosen grid name from the query parameters gridname = request.args.get('gridname') # Open the corresponding file and read its contents as json # and recreate the grid from it jsonstr = grid_load_common(userid, gridname) grid = Grid.from_json(jsonstr) # Get the top two word lengths heading_list = [f"{grid.get_word_count()} words"] wlens = grid.get_word_lengths() wlenkeys = sorted(wlens.keys(), reverse=True) wlenkeys = wlenkeys[:min(2, len(wlenkeys))] for wlen in wlenkeys: entry = wlens[wlen] total = 0 if entry["alist"]: total += len(entry["alist"]) if entry["dlist"]: total += len(entry["dlist"]) heading_list.append(f"{wlen}-letter: {total}") heading = f'Grid {gridname}({", ".join(heading_list)})' scale = 0.75 svgobj = GridToSVG(grid, scale=scale) width = (svgobj.boxsize * grid.n + 32) * scale svgstr = svgobj.generate_xml() obj = { "gridname": gridname, "heading": heading, "width": width, "svgstr": svgstr } resp = make_response(json.dumps(obj), HTTPStatus.OK) resp.headers['Content-Type'] = "application/json" return resp
def grid_click(): """ Adds or removes a black cell then returns the new SVG """ # Get the row and column clicked from the query parms r = int(request.args.get('r')) c = int(request.args.get('c')) # Get the existing grid from the session jsonstr = session['grid'] grid = Grid.from_json(jsonstr) # Toggle the black cell status if grid.is_black_cell(r, c): grid.remove_black_cell(r, c) else: grid.add_black_cell(r, c) # Save the updated grid in the session session['grid'] = grid.to_json() return redirect(url_for('uigrid.grid_screen'))
def puzzle_replace_grid(): # Get the chosen grid name from the query parameters gridname = request.args.get('gridname') if not gridname: return redirect(url_for('uipuzzle.puzzle_screen')) # Create the grid userid = 1 # TODO replace hard-coded user ID query = DBGrid.query.filter_by(userid=userid, gridname=gridname) jsonstr = query.first().jsonstr grid = Grid.from_json(jsonstr) # Get the current puzzle puzzle = Puzzle.from_json(session['puzzle']) # and replace its grid puzzle.replace_grid(grid) # Save in the session session['puzzle'] = puzzle.to_json() return redirect(url_for('uipuzzle.puzzle_screen'))
def test_new_grid(self): puzzle = TestPuzzle.create_solved_atlantic_puzzle() oldjson = puzzle.to_json() grid = Grid.from_json(puzzle.to_json()) grid.add_black_cell(4, 4) puzzle.replace_grid(grid) newjson = puzzle.to_json() import json old = json.loads(oldjson) new = json.loads(newjson) # Compare black cells self.assertIn([4, 4], new['black_cells']) self.assertIn([6, 6], new['black_cells']) new['black_cells'].remove([4, 4]) new['black_cells'].remove([6, 6]) self.assertListEqual(old['black_cells'], new['black_cells']) # Compare numbered cells expected = [ NumberedCell(seq=1, r=1, c=1, a=3, d=4), NumberedCell(seq=2, r=1, c=2, a=0, d=4), NumberedCell(seq=3, r=1, c=3, a=0, d=9), NumberedCell(seq=4, r=1, c=6, a=4, d=5), NumberedCell(seq=5, r=1, c=7, a=0, d=9), NumberedCell(seq=6, r=1, c=8, a=0, d=4), NumberedCell(seq=7, r=1, c=9, a=0, d=3), NumberedCell(seq=8, r=2, c=1, a=4, d=0), NumberedCell(seq=9, r=2, c=4, a=0, d=2), NumberedCell(seq=10, r=2, c=6, a=4, d=0), NumberedCell(seq=11, r=3, c=1, a=9, d=0), NumberedCell(seq=12, r=3, c=5, a=0, d=5), NumberedCell(seq=13, r=4, c=1, a=3, d=0), NumberedCell(seq=14, r=4, c=5, a=4, d=0), NumberedCell(seq=15, r=5, c=3, a=5, d=0), NumberedCell(seq=16, r=5, c=4, a=0, d=5), NumberedCell(seq=17, r=6, c=2, a=4, d=4), NumberedCell(seq=18, r=6, c=7, a=3, d=0), NumberedCell(seq=19, r=6, c=8, a=0, d=4), NumberedCell(seq=20, r=6, c=9, a=0, d=4), NumberedCell(seq=21, r=7, c=1, a=9, d=3), NumberedCell(seq=22, r=7, c=6, a=0, d=2), NumberedCell(seq=23, r=8, c=1, a=4, d=0), NumberedCell(seq=24, r=8, c=6, a=4, d=0), NumberedCell(seq=25, r=9, c=1, a=4, d=0), NumberedCell(seq=26, r=9, c=7, a=3, d=0), ] actual = [] for x in new['numbered_cells']: jsonstr = json.dumps(x) nc = NumberedCell.from_json(jsonstr) actual.append(nc) self.assertListEqual(expected, actual) # Compare clues oldclues = { x['text']: x['clue'] for x in old['across_words'] + old['down_words'] } newclues = { x['text']: x['clue'] for x in new['across_words'] + new['down_words'] } for k, v in newclues.items(): if k in oldclues: oldclue = oldclues[k] self.assertEqual(oldclue, newclues[k])
def test_same_grid(self): oldpuzzle = TestPuzzle.create_solved_atlantic_puzzle() grid = Grid.from_json(oldpuzzle.to_json()) newpuzzle = Puzzle.from_json(oldpuzzle.to_json()) newpuzzle.replace_grid(grid) self.assertEqual(oldpuzzle, newpuzzle)
def get_good_grid(): jsonstr = """ { "n": 15, "black_cells": [ [ 1, 4 ], [ 1, 5 ], [ 1, 11 ], [ 2, 5 ], [ 2, 11 ], [ 3, 5 ], [ 3, 11 ], [ 4, 1 ], [ 4, 2 ], [ 4, 8 ], [ 4, 14 ], [ 4, 15 ], [ 5, 1 ], [ 5, 2 ], [ 5, 3 ], [ 5, 7 ], [ 5, 8 ], [ 5, 13 ], [ 5, 14 ], [ 5, 15 ], [ 6, 8 ], [ 6, 9 ], [ 6, 10 ], [ 7, 5 ], [ 7, 11 ], [ 8, 5 ], [ 8, 11 ], [ 9, 5 ], [ 9, 11 ], [ 10, 6 ], [ 10, 7 ], [ 10, 8 ], [ 11, 1 ], [ 11, 2 ], [ 11, 3 ], [ 11, 8 ], [ 11, 9 ], [ 11, 13 ], [ 11, 14 ], [ 11, 15 ], [ 12, 1 ], [ 12, 2 ], [ 12, 8 ], [ 12, 14 ], [ 12, 15 ], [ 13, 5 ], [ 13, 11 ], [ 14, 5 ], [ 14, 11 ], [ 15, 5 ], [ 15, 11 ], [ 15, 12 ] ], "numbered_cells": [ { "seq": 1, "r": 1, "c": 1, "a": 3, "d": 3 }, { "seq": 2, "r": 1, "c": 2, "a": 0, "d": 3 }, { "seq": 3, "r": 1, "c": 3, "a": 0, "d": 4 }, { "seq": 4, "r": 1, "c": 6, "a": 5, "d": 9 }, { "seq": 5, "r": 1, "c": 7, "a": 0, "d": 4 }, { "seq": 6, "r": 1, "c": 8, "a": 0, "d": 3 }, { "seq": 7, "r": 1, "c": 9, "a": 0, "d": 5 }, { "seq": 8, "r": 1, "c": 10, "a": 0, "d": 5 }, { "seq": 9, "r": 1, "c": 12, "a": 4, "d": 14 }, { "seq": 10, "r": 1, "c": 13, "a": 0, "d": 4 }, { "seq": 11, "r": 1, "c": 14, "a": 0, "d": 3 }, { "seq": 12, "r": 1, "c": 15, "a": 0, "d": 3 }, { "seq": 13, "r": 2, "c": 1, "a": 4, "d": 0 }, { "seq": 14, "r": 2, "c": 4, "a": 0, "d": 14 }, { "seq": 15, "r": 2, "c": 6, "a": 5, "d": 0 }, { "seq": 16, "r": 2, "c": 12, "a": 4, "d": 0 }, { "seq": 17, "r": 3, "c": 1, "a": 4, "d": 0 }, { "seq": 18, "r": 3, "c": 6, "a": 5, "d": 0 }, { "seq": 19, "r": 3, "c": 12, "a": 4, "d": 0 }, { "seq": 20, "r": 4, "c": 3, "a": 5, "d": 0 }, { "seq": 21, "r": 4, "c": 5, "a": 0, "d": 3 }, { "seq": 22, "r": 4, "c": 9, "a": 5, "d": 0 }, { "seq": 23, "r": 4, "c": 11, "a": 0, "d": 3 }, { "seq": 24, "r": 5, "c": 4, "a": 3, "d": 0 }, { "seq": 25, "r": 5, "c": 9, "a": 4, "d": 0 }, { "seq": 26, "r": 6, "c": 1, "a": 7, "d": 5 }, { "seq": 27, "r": 6, "c": 2, "a": 0, "d": 5 }, { "seq": 28, "r": 6, "c": 3, "a": 0, "d": 5 }, { "seq": 29, "r": 6, "c": 7, "a": 0, "d": 4 }, { "seq": 30, "r": 6, "c": 11, "a": 5, "d": 0 }, { "seq": 31, "r": 6, "c": 13, "a": 0, "d": 5 }, { "seq": 32, "r": 6, "c": 14, "a": 0, "d": 5 }, { "seq": 33, "r": 6, "c": 15, "a": 0, "d": 5 }, { "seq": 34, "r": 7, "c": 1, "a": 4, "d": 0 }, { "seq": 35, "r": 7, "c": 6, "a": 5, "d": 0 }, { "seq": 36, "r": 7, "c": 8, "a": 0, "d": 3 }, { "seq": 37, "r": 7, "c": 9, "a": 0, "d": 4 }, { "seq": 38, "r": 7, "c": 10, "a": 0, "d": 9 }, { "seq": 39, "r": 7, "c": 12, "a": 4, "d": 0 }, { "seq": 40, "r": 8, "c": 1, "a": 4, "d": 0 }, { "seq": 41, "r": 8, "c": 6, "a": 5, "d": 0 }, { "seq": 42, "r": 8, "c": 12, "a": 4, "d": 0 }, { "seq": 43, "r": 9, "c": 1, "a": 4, "d": 0 }, { "seq": 44, "r": 9, "c": 6, "a": 5, "d": 0 }, { "seq": 45, "r": 9, "c": 12, "a": 4, "d": 0 }, { "seq": 46, "r": 10, "c": 1, "a": 5, "d": 0 }, { "seq": 47, "r": 10, "c": 5, "a": 0, "d": 3 }, { "seq": 48, "r": 10, "c": 9, "a": 7, "d": 0 }, { "seq": 49, "r": 10, "c": 11, "a": 0, "d": 3 }, { "seq": 50, "r": 11, "c": 4, "a": 4, "d": 0 }, { "seq": 51, "r": 11, "c": 6, "a": 0, "d": 5 }, { "seq": 52, "r": 11, "c": 7, "a": 0, "d": 5 }, { "seq": 53, "r": 11, "c": 10, "a": 3, "d": 0 }, { "seq": 54, "r": 12, "c": 3, "a": 5, "d": 4 }, { "seq": 55, "r": 12, "c": 9, "a": 5, "d": 4 }, { "seq": 56, "r": 12, "c": 13, "a": 0, "d": 4 }, { "seq": 57, "r": 13, "c": 1, "a": 4, "d": 3 }, { "seq": 58, "r": 13, "c": 2, "a": 0, "d": 3 }, { "seq": 59, "r": 13, "c": 6, "a": 5, "d": 0 }, { "seq": 60, "r": 13, "c": 8, "a": 0, "d": 3 }, { "seq": 61, "r": 13, "c": 12, "a": 4, "d": 0 }, { "seq": 62, "r": 13, "c": 14, "a": 0, "d": 3 }, { "seq": 63, "r": 13, "c": 15, "a": 0, "d": 3 }, { "seq": 64, "r": 14, "c": 1, "a": 4, "d": 0 }, { "seq": 65, "r": 14, "c": 6, "a": 5, "d": 0 }, { "seq": 66, "r": 14, "c": 12, "a": 4, "d": 0 }, { "seq": 67, "r": 15, "c": 1, "a": 4, "d": 0 }, { "seq": 68, "r": 15, "c": 6, "a": 5, "d": 0 }, { "seq": 69, "r": 15, "c": 13, "a": 3, "d": 0 } ] } """ grid = Grid.from_json(jsonstr) return grid
def test_grid_from_puzzle(self): puzzle = TestPuzzle.create_nyt_daily() jsonstr = puzzle.to_json() grid = Grid.from_json(jsonstr) self.assertEqual(puzzle.n, grid.n)
def get_good_grid(): jsonstr = """ { "n": 9, "cells": [ "+-----------------+", "|*| | | |*| | | | |", "|*| | | |*| | | | |", "| | | | |*| | | | |", "| | | | | |*| | | |", "| | | | | | | | | |", "| | | |*| | | | | |", "| | | | |*| | | | |", "| | | | |*| | | |*|", "| | | | |*| | | |*|", "+-----------------+" ], "black_cells": [ [ 1, 1 ], [ 1, 5 ], [ 2, 1 ], [ 2, 5 ], [ 3, 5 ], [ 4, 6 ], [ 6, 4 ], [ 7, 5 ], [ 8, 5 ], [ 8, 9 ], [ 9, 5 ], [ 9, 9 ] ], "numbered_cells": [ { "seq": 1, "r": 1, "c": 2, "a": 3, "d": 9 }, { "seq": 2, "r": 1, "c": 3, "a": 0, "d": 9 }, { "seq": 3, "r": 1, "c": 4, "a": 0, "d": 5 }, { "seq": 4, "r": 1, "c": 6, "a": 4, "d": 3 }, { "seq": 5, "r": 1, "c": 7, "a": 0, "d": 9 }, { "seq": 6, "r": 1, "c": 8, "a": 0, "d": 9 }, { "seq": 7, "r": 1, "c": 9, "a": 0, "d": 7 }, { "seq": 8, "r": 2, "c": 2, "a": 3, "d": 0 }, { "seq": 9, "r": 2, "c": 6, "a": 4, "d": 0 }, { "seq": 10, "r": 3, "c": 1, "a": 4, "d": 7 }, { "seq": 11, "r": 3, "c": 6, "a": 4, "d": 0 }, { "seq": 12, "r": 4, "c": 1, "a": 5, "d": 0 }, { "seq": 13, "r": 4, "c": 5, "a": 0, "d": 3 }, { "seq": 14, "r": 4, "c": 7, "a": 3, "d": 0 }, { "seq": 15, "r": 5, "c": 1, "a": 9, "d": 0 }, { "seq": 16, "r": 5, "c": 6, "a": 0, "d": 5 }, { "seq": 17, "r": 6, "c": 1, "a": 3, "d": 0 }, { "seq": 18, "r": 6, "c": 5, "a": 5, "d": 0 }, { "seq": 19, "r": 7, "c": 1, "a": 4, "d": 0 }, { "seq": 20, "r": 7, "c": 4, "a": 0, "d": 3 }, { "seq": 21, "r": 7, "c": 6, "a": 4, "d": 0 }, { "seq": 22, "r": 8, "c": 1, "a": 4, "d": 0 }, { "seq": 23, "r": 8, "c": 6, "a": 3, "d": 0 }, { "seq": 24, "r": 9, "c": 1, "a": 4, "d": 0 }, { "seq": 25, "r": 9, "c": 6, "a": 3, "d": 0 } ] } """ grid = Grid.from_json(jsonstr) return grid