예제 #1
0
def get_word(session):
    """ Internal method to get
    the puzzle, seq, direction, length, and word from the session """

    # Reconstruct the puzzle from the JSON stored in the session
    jsonstr = session.get('puzzle', None)
    puzzle = Puzzle.from_json(jsonstr)

    # Get the word location (seq, direction), like "25 A" for 25 across
    seq = session.get('seq', None)

    direction = session.get('direction', None)
    direction = Word.ACROSS if direction.upper(
    )[0] == Word.ACROSS else Word.DOWN
    if direction == Word.ACROSS:
        word = puzzle.get_across_word(seq)
    elif direction == Word.DOWN:
        word = puzzle.get_down_word(seq)
    else:
        raise RuntimeError("Direction is not A or D")
    pass

    # Get the word length
    length = session.get('length', None)

    # Return all this to the caller
    return puzzle, seq, direction, length, word
예제 #2
0
    def __init__(self, user, puzzlename, filename, filetype=None):
        """ Creates a PuzzleExport object

        :param user a DBUser
        :param puzzlename the name of the puzzle to be exported
        :param filename the output file name
        :param filetype one of 'json', 'xml', or 'csv' (optional)
        """
        self.user = user
        self.puzzlename = puzzlename
        self.filename = filename
        if not filetype:
            base, ext = os.path.splitext(filename)
            if not ext:
                errmsg = "Unable to determine file type from extension"
                raise ValueError(errmsg)
            filetype = ext
        filetype = filetype.upper()
        if filetype.startswith("."):
            filetype = filetype[1:]
        if filetype not in ['JSON', 'XML', 'CSV']:
            errmsg = f"File type must be JSON, XML, or CSV, not {filetype}"
            raise ValueError(errmsg)
        self.filetype = filetype

        # Load the puzzle
        userid = user.id
        row = DBPuzzle.query.filter_by(userid=userid,
                                       puzzlename=puzzlename).first()
        jsonstr = row.jsonstr
        self.puzzle = Puzzle.from_json(jsonstr)
예제 #3
0
def puzzle_redo():
    """ Redoes the last puzzle action then redirects to puzzle screen """

    jsonstr = session.get('puzzle', None)
    puzzle = Puzzle.from_json(jsonstr)
    puzzle.redo()
    jsonstr = puzzle.to_json()
    session['puzzle'] = jsonstr
    return redirect(url_for('uipuzzle.puzzle_screen'))
예제 #4
0
def puzzle_title():
    """ Changes the puzzle title and redirects back to the puzzle screen """

    title = request.form.get('ib-input', None)
    if title:
        jsonstr = session['puzzle']
        puzzle = Puzzle.from_json(jsonstr)
        puzzle.title = title
        jsonstr = puzzle.to_json()
        session['puzzle'] = jsonstr
        flash(f"Puzzle title set to {puzzle.title}")

    # Show the puzzle screen
    return redirect(url_for('uipuzzle.puzzle_screen'))
예제 #5
0
def puzzle_save_common(puzzlename):
    """ Common method used by both puzzle_save and puzzle_save_as """

    # Recreate the puzzle from the JSON in the session
    # and validate it
    jsonstr = session.get('puzzle', None)
    puzzle = Puzzle.from_json(jsonstr)
    jsonstr = puzzle.to_json()
    ok, messages = puzzle.validate()
    if not ok:
        flash("Puzzle 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 = DBPuzzle.query.filter_by(userid=userid, puzzlename=puzzlename)
        if not query.all():
            # No puzzle in the database. This is an insert
            logging.debug(f"Inserting puzzle {puzzlename} into puzzles table")
            created = modified = datetime.now().isoformat()
            newpuzzle = DBPuzzle(userid=userid,
                                 puzzlename=puzzlename,
                                 created=created,
                                 modified=modified,
                                 jsonstr=jsonstr)
            db.session.add(newpuzzle)
            db.session.commit()
        else:
            # Existing puzzle. This is an update
            logging.debug(f"Updating puzzle {puzzlename} in puzzles table")
            oldpuzzle = query.first()
            oldpuzzle.modified = datetime.now().isoformat()
            oldpuzzle.jsonstr = jsonstr
            db.session.commit()

        # Send message about the save
        flash(f"Puzzle saved as {puzzlename}")

        # Store the sha of the saved version of the puzzle
        # in the session as 'puzzle.initial.sha' so that we can
        # detect whether it has been changed since it was last saved
        session['puzzle.initial.sha'] = sha256(puzzle.to_json())

    # Show the puzzle screen
    return redirect(url_for('uipuzzle.puzzle_screen'))
예제 #6
0
    def run_csv(self):
        """ Uploads puzzle clues from CSV input """
        csvstr = self.data
        user = self.user
        userid = user.id
        puzzlename = self.puzzlename
        puzzle = DBPuzzle.query.filter_by(userid=userid,
                                          puzzlename=puzzlename).first()
        if not puzzle:
            errmsg = f"Puzzle '{puzzlename}' does not exist"
            raise ValueError(errmsg)
        jsonstr = puzzle.jsonstr
        puzzle = Puzzle.from_json(jsonstr)

        with StringIO(csvstr) as fp:
            cfp = csv.reader(fp)
            next(cfp)  # Skip column headings
            for row in cfp:
                seq = int(row[0])
                direction = row[1]
                text = row[2]
                clue_text = row[3]
                if direction == 'across':
                    word = puzzle.get_across_word(seq)
                    if not word:
                        errmsg = f'{seq} across is not defined'
                        raise RuntimeError(errmsg)
                    previous_text = word.get_text()
                    if text != previous_text:
                        errmsg = f'Word at {seq} across should be "{previous_text}", not "{text}"'
                        raise RuntimeError(errmsg)
                    word.set_clue(clue_text)
                elif direction == 'down':
                    word = puzzle.get_down_word(seq)
                    if not word:
                        errmsg = f'{seq} down is not defined'
                        raise RuntimeError(errmsg)
                    previous_text = word.get_text()
                    if text != previous_text:
                        errmsg = f'Word at {seq} down should be "{previous_text}", not "{text}"'
                        raise RuntimeError(errmsg)
                    word.set_clue(clue_text)
                else:
                    errmsg = f'Direction is "{direction}", not "across" or "down"'
                    raise RuntimeError(errmsg)

        return puzzle
예제 #7
0
def puzzle_publish_nytimes():
    """ Publishes a puzzle in New York Times format (PDF) """

    # Get the chosen puzzle name from the query parameters
    puzzlename = request.args.get('puzzlename')

    # Open the corresponding row in the database, read its JSON
    # and recreate the puzzle from it
    jsonstr = puzzle_load_common(userid, puzzlename)
    puzzle = Puzzle.from_json(jsonstr)

    # Generate the output
    publisher = PuzzlePublishNYTimes(puzzle, puzzlename)

    # SVG
    filename = os.path.join(tempfile.gettempdir(), puzzlename + ".svg")
    svg_filename = filename
    with open(filename, "wt") as fp:
        fp.write(publisher.get_svg() + "\n")

    # HTML
    filename = os.path.join(tempfile.gettempdir(), puzzlename + ".html")
    html_filename = filename
    with open(filename, "wt") as fp:
        fp.write(publisher.get_html() + "\n")

    # JSON
    filename = os.path.join(tempfile.gettempdir(), puzzlename + ".json")
    json_filename = filename
    with open(filename, "wt") as fp:
        fp.write(jsonstr + "\n")

    # Create an in-memory zip file
    with BytesIO() as fp:
        with ZipFile(fp, mode="w", compression=ZIP_DEFLATED) as zf:
            zf.write(svg_filename, puzzlename + ".svg")
            zf.write(html_filename, puzzlename + ".html")
            zf.write(json_filename, puzzlename + ".json")
        zipbytes = fp.getvalue()

    # Return it as an attachment
    zipfilename = f"nytimes-{puzzlename}.zip"
    resp = make_response(zipbytes)
    resp.headers['Content-Type'] = "application/zip"
    resp.headers[
        'Content-Disposition'] = f'attachment; filename="{zipfilename}"'
    return resp
예제 #8
0
def puzzle_open():
    """ Opens a new puzzle and redirects to puzzle screen """

    # Get the chosen puzzle name from the query parameters
    puzzlename = request.args.get('puzzlename')

    # Open the corresponding file and read its contents as json
    # and recreate the puzzle from it
    userid = 1  # TODO Replace hard coded user id
    jsonstr = puzzle_load_common(userid, puzzlename)
    puzzle = Puzzle.from_json(jsonstr)

    # Store the puzzle and puzzle name in the session
    session['puzzle'] = jsonstr
    session['puzzle.initial.sha'] = sha256(jsonstr)
    session['puzzlename'] = puzzlename

    return redirect(url_for('uipuzzle.puzzle_screen'))
예제 #9
0
def puzzle_changed():
    """ REST method that returns whether the puzzle has changed since it was opened """

    current_puzzle_json = session.get('puzzle', None)
    if current_puzzle_json:
        current_puzzle = Puzzle.from_json(current_puzzle_json)
        jsonstr_initial_sha = session.get('puzzle.initial.sha', sha256(None))
        jsonstr_current_sha = sha256(current_puzzle.to_json())
        changed = not (jsonstr_current_sha == jsonstr_initial_sha)
    else:
        changed = False

    obj = {"changed": changed}

    # Send this back to the client in JSON
    resp = make_response(json.dumps(obj), HTTPStatus.OK)
    resp.headers['Content-Type'] = "application/json"
    return resp
예제 #10
0
def puzzle_screen():
    """ Renders the puzzle screen """

    # Get the existing puzzle from the session
    puzzle = Puzzle.from_json(session['puzzle'])
    puzzlename = session.get('puzzlename', None)

    # Get the clues
    clues = {
        "across": [{
            "seq": seq,
            "text": word.get_clue() or ""
        } for seq, word in puzzle.across_words.items()],
        "down": [{
            "seq": seq,
            "text": word.get_clue() or ""
        } for seq, word in puzzle.down_words.items()],
    }

    # Create the SVG
    svg = PuzzleToSVG(puzzle)
    boxsize = svg.boxsize
    svgstr = svg.generate_xml()

    # Set the state to editing puzzle
    session['uistate'] = UIState.EDITING_PUZZLE
    enabled = session['uistate'].get_enabled()
    enabled["puzzle_undo"] = len(puzzle.undo_stack) > 0
    enabled["puzzle_redo"] = len(puzzle.redo_stack) > 0
    enabled["puzzle_delete"] = puzzlename is not None

    # Send puzzle.html to the client
    return render_template('puzzle.html',
                           enabled=enabled,
                           puzzlename=puzzlename,
                           puzzletitle=puzzle.title,
                           n=puzzle.n,
                           clues=clues,
                           scrollTopAcross=session.get('scrollTopAcross',
                                                       None),
                           scrollTopDown=session.get('scrollTopDown', None),
                           boxsize=boxsize,
                           svgstr=svgstr)
예제 #11
0
def puzzle_statistics():
    """ Return the grid statistics in a JSON string """

    # Get the grid from the session
    puzzle = Puzzle.from_json(session['puzzle'])
    puzzlename = session.get('puzzlename', None)
    puzzletitle = session.get('puzzletitle', None)
    stats = puzzle.get_statistics()
    enabled = {}

    svgstr = PuzzleToSVG(puzzle).generate_xml()

    # Render with puzzle statistics template
    return render_template("puzzle-statistics.html",
                           enabled=enabled,
                           puzzlename=puzzlename,
                           puzzletitle=puzzletitle,
                           svgstr=svgstr,
                           stats=stats)
예제 #12
0
def puzzle_preview():
    """ Creates a puzzle preview and returns it to ??? """
    userid = 1  # TODO Replace hard coded user id

    # Get the chosen puzzle name from the query parameters
    puzzlename = request.args.get('puzzlename')

    # Open the corresponding file and read its contents as json
    # and recreate the puzzle from it
    jsonstr = puzzle_load_common(userid, puzzlename)
    puzzle = Puzzle.from_json(jsonstr)

    # Get the top two word lengths
    heading_list = [f"{puzzle.get_word_count()} words"]
    wlens = puzzle.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'Puzzle {puzzlename}({", ".join(heading_list)})'

    scale = 0.75
    svgobj = PuzzleToSVG(puzzle, scale=scale)
    width = (svgobj.boxsize * puzzle.n + 32) * scale
    svgstr = svgobj.generate_xml()

    obj = {
        "puzzlename": puzzlename,
        "heading": heading,
        "width": width,
        "svgstr": svgstr
    }
    resp = make_response(json.dumps(obj), HTTPStatus.OK)
    resp.headers['Content-Type'] = "application/json"
    return resp
예제 #13
0
def puzzle_publish_cwcompiler():
    """ Publishes a puzzle in the Crossword Compiler XML format """

    # Get the chosen puzzle name from the query parameters
    puzzlename = request.args.get('puzzlename')

    # Open the corresponding row in the database, read its JSON
    # and recreate the puzzle from it
    jsonstr = puzzle_load_common(userid, puzzlename)
    puzzle = Puzzle.from_json(jsonstr)

    # Generate the output
    user = DBUser.query.filter_by(id=userid).first()
    publisher = PuzzleToXML(user, puzzle)

    # XML file
    xml_filename = filename = os.path.join(tempfile.gettempdir(),
                                           puzzlename + ".xml")
    with open(filename, "wt") as fp:
        fp.write(publisher.xmlstr + "\n")

    # JSON
    json_filename = filename = os.path.join(tempfile.gettempdir(),
                                            puzzlename + ".json")
    with open(filename, "wt") as fp:
        fp.write(jsonstr + "\n")

    # Create an in-memory zip file
    with BytesIO() as fp:
        with ZipFile(fp, mode="w", compression=ZIP_DEFLATED) as zf:
            zf.write(xml_filename, puzzlename + ".xml")
            zf.write(json_filename, puzzlename + ".json")
        zipbytes = fp.getvalue()

    # Return it as an attachment
    zipfilename = f"cwcompiler-{puzzlename}.zip"
    resp = make_response(zipbytes)
    resp.headers['Content-Type'] = "application/zip"
    resp.headers[
        'Content-Disposition'] = f'attachment; filename="{zipfilename}"'
    return resp
예제 #14
0
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'))
예제 #15
0
    def test_load_atlantic(self):

        # Make sure the saved file is there: create it in /tmp
        self.test_save()
        filename = os.path.join(tempfile.gettempdir(),
                                "test_puzzle.test_save.json")

        # Load the file
        with open(filename, "rt") as fp:
            jsonstr = fp.read()
        puzzle = Puzzle.from_json(jsonstr)

        # Check some contents

        self.assertEqual(9, puzzle.n)

        word = puzzle.get_across_word(8)
        self.assertEqual("SLIM", word.get_text())
        self.assertEqual("Reed-like", word.get_clue())

        word = puzzle.get_down_word(17)
        self.assertEqual("EHRE", word.get_text())
        self.assertEqual("Honor, to Fritz", word.get_clue())
예제 #16
0
    def create_puzzle():
        jsonstr = r'''{
  "n": 15,
  "black_cells": [
[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],[9,12],[10,7],[10,8],[10,13],[10,14],
[10,15],[11,4],[11,9],[12,1],[12,2],[13,5],
[13,11],[14,5],[14,11],[15,5],[15,11]],
  "numbered_cells": [
    { "seq": 1, "r": 1, "c": 1, "a": 4, "d": 5 },
    { "seq": 2, "r": 1, "c": 2, "a": 0, "d": 5 },
    { "seq": 3, "r": 1, "c": 3, "a": 0, "d": 5 },
    { "seq": 4, "r": 1, "c": 4, "a": 0, "d": 6 },
    { "seq": 5, "r": 1, "c": 6, "a": 5, "d": 7 },
    { "seq": 6, "r": 1, "c": 7, "a": 0, "d": 4 },
    { "seq": 7, "r": 1, "c": 8, "a": 0, "d": 5 },
    { "seq": 8, "r": 1, "c": 9, "a": 0, "d": 5 },
    { "seq": 9, "r": 1, "c": 10, "a": 0, "d": 7 },
    { "seq": 10, "r": 1, "c": 12, "a": 4, "d": 4 },
    { "seq": 11, "r": 1, "c": 13, "a": 0, "d": 9 },
    { "seq": 12, "r": 1, "c": 14, "a": 0, "d": 3 },
    { "seq": 13, "r": 1, "c": 15, "a": 0, "d": 3 },
    { "seq": 14, "r": 2, "c": 1, "a": 4, "d": 0 },
    { "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": 1, "a": 13, "d": 0 },
    { "seq": 21, "r": 4, "c": 5, "a": 0, "d": 4 },
    { "seq": 22, "r": 4, "c": 11, "a": 0, "d": 4 },
    { "seq": 23, "r": 5, "c": 1, "a": 6, "d": 0 },
    { "seq": 24, "r": 5, "c": 8, "a": 4, "d": 0 },
    { "seq": 25, "r": 5, "c": 13, "a": 3, "d": 0 },
    { "seq": 26, "r": 5, "c": 14, "a": 0, "d": 5 },
    { "seq": 27, "r": 5, "c": 15, "a": 0, "d": 5 },
    { "seq": 28, "r": 6, "c": 4, "a": 4, "d": 0 },
    { "seq": 29, "r": 6, "c": 7, "a": 0, "d": 4 },
    { "seq": 30, "r": 6, "c": 10, "a": 6, "d": 0 },
    { "seq": 31, "r": 6, "c": 12, "a": 0, "d": 3 },
    { "seq": 32, "r": 7, "c": 1, "a": 3, "d": 5 },
    { "seq": 33, "r": 7, "c": 2, "a": 0, "d": 5 },
    { "seq": 34, "r": 7, "c": 3, "a": 0, "d": 9 },
    { "seq": 35, "r": 7, "c": 5, "a": 11, "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": 8, "c": 1, "a": 4, "d": 0 },
    { "seq": 39, "r": 8, "c": 4, "a": 0, "d": 3 },
    { "seq": 40, "r": 8, "c": 7, "a": 3, "d": 0 },
    { "seq": 41, "r": 8, "c": 12, "a": 4, "d": 0 },
    { "seq": 42, "r": 9, "c": 1, "a": 11, "d": 0 },
    { "seq": 43, "r": 9, "c": 5, "a": 0, "d": 4 },
    { "seq": 44, "r": 9, "c": 6, "a": 0, "d": 7 },
    { "seq": 45, "r": 9, "c": 10, "a": 0, "d": 7 },
    { "seq": 46, "r": 9, "c": 11, "a": 0, "d": 4 },
    { "seq": 47, "r": 9, "c": 13, "a": 3, "d": 0 },
    { "seq": 48, "r": 10, "c": 1, "a": 6, "d": 0 },
    { "seq": 49, "r": 10, "c": 9, "a": 4, "d": 0 },
    { "seq": 50, "r": 10, "c": 12, "a": 0, "d": 6 },
    { "seq": 51, "r": 11, "c": 1, "a": 3, "d": 0 },
    { "seq": 52, "r": 11, "c": 5, "a": 4, "d": 0 },
    { "seq": 53, "r": 11, "c": 7, "a": 0, "d": 5 },
    { "seq": 54, "r": 11, "c": 8, "a": 0, "d": 5 },
    { "seq": 55, "r": 11, "c": 10, "a": 6, "d": 0 },
    { "seq": 56, "r": 11, "c": 13, "a": 0, "d": 5 },
    { "seq": 57, "r": 11, "c": 14, "a": 0, "d": 5 },
    { "seq": 58, "r": 11, "c": 15, "a": 0, "d": 5 },
    { "seq": 59, "r": 12, "c": 3, "a": 13, "d": 0 },
    { "seq": 60, "r": 12, "c": 4, "a": 0, "d": 4 },
    { "seq": 61, "r": 12, "c": 9, "a": 0, "d": 4 },
    { "seq": 62, "r": 13, "c": 1, "a": 4, "d": 3 },
    { "seq": 63, "r": 13, "c": 2, "a": 0, "d": 3 },
    { "seq": 64, "r": 13, "c": 6, "a": 5, "d": 0 },
    { "seq": 65, "r": 13, "c": 12, "a": 4, "d": 0 },
    { "seq": 66, "r": 14, "c": 1, "a": 4, "d": 0 },
    { "seq": 67, "r": 14, "c": 6, "a": 5, "d": 0 },
    { "seq": 68, "r": 14, "c": 12, "a": 4, "d": 0 },
    { "seq": 69, "r": 15, "c": 1, "a": 4, "d": 0 },
    { "seq": 70, "r": 15, "c": 6, "a": 5, "d": 0 },
    { "seq": 71, "r": 15, "c": 12, "a": 4, "d": 0 }
  ],
  "across_words": [
    { "seq": 1, "text": "ACTS", "clue": "____ of the Apostles" },
    { "seq": 5, "text": "PLASM", "clue": "Suffix with ecto- or proto-" },
    { "seq": 10, "text": "ENVY", "clue": "" },
    { "seq": 14, "text": "SHIM", "clue": "Blade in the pen" },
    { "seq": 15, "text": "     ", "clue": null },
    { "seq": 16, "text": "V   ", "clue": null },
    { "seq": 17, "text": "NINE", "clue": "The Nazgul sum" },
    { "seq": 18, "text": "     ", "clue": null },
    { "seq": 19, "text": "I   ", "clue": null },
    { "seq": 20, "text": "ENTERTAINMENT", "clue": "" },
    { "seq": 23, "text": "RASCAL", "clue": "" },
    { "seq": 24, "text": "    ", "clue": null },
    { "seq": 25, "text": "   ", "clue": null },
    { "seq": 28, "text": "H   ", "clue": "" },
    { "seq": 30, "text": "      ", "clue": null },
    { "seq": 32, "text": "   ", "clue": null },
    { "seq": 35, "text": "           ", "clue": null },
    { "seq": 38, "text": "    ", "clue": null },
    { "seq": 40, "text": "   ", "clue": null },
    { "seq": 41, "text": "    ", "clue": null },
    { "seq": 42, "text": "           ", "clue": null },
    { "seq": 47, "text": "   ", "clue": null },
    { "seq": 48, "text": "      ", "clue": null },
    { "seq": 49, "text": "    ", "clue": null },
    { "seq": 51, "text": "   ", "clue": null },
    { "seq": 52, "text": "    ", "clue": null },
    { "seq": 55, "text": "      ", "clue": null },
    { "seq": 59, "text": "             ", "clue": null },
    { "seq": 62, "text": "    ", "clue": null },
    { "seq": 64, "text": "     ", "clue": null },
    { "seq": 65, "text": "    ", "clue": null },
    { "seq": 66, "text": "    ", "clue": null },
    { "seq": 67, "text": "     ", "clue": null },
    { "seq": 68, "text": "    ", "clue": null },
    { "seq": 69, "text": "    ", "clue": null },
    { "seq": 70, "text": "     ", "clue": null },
    { "seq": 71, "text": "    ", "clue": null }
  ],
  "down_words": [
    { "seq": 1, "text": "ASNER", "clue": "Ed of \"Up\"" },
    { "seq": 2, "text": "CHINA", "clue": "" },
    { "seq": 3, "text": "TINTS", "clue": null },
    { "seq": 4, "text": "SMEECH", "clue": null },
    { "seq": 5, "text": "P  TL  ", "clue": null },
    { "seq": 6, "text": "L  A", "clue": null },
    { "seq": 7, "text": "A  I ", "clue": null },
    { "seq": 8, "text": "S  N ", "clue": null },
    { "seq": 9, "text": "M  M   ", "clue": null },
    { "seq": 10, "text": "EVIN", "clue": "" },
    { "seq": 11, "text": "N  T     ", "clue": null },
    { "seq": 12, "text": "V  ", "clue": null },
    { "seq": 13, "text": "Y  ", "clue": null },
    { "seq": 21, "text": "RA  ", "clue": null },
    { "seq": 22, "text": "E   ", "clue": null },
    { "seq": 26, "text": "     ", "clue": null },
    { "seq": 27, "text": "     ", "clue": null },
    { "seq": 29, "text": "    ", "clue": null },
    { "seq": 31, "text": "   ", "clue": null },
    { "seq": 32, "text": "     ", "clue": null },
    { "seq": 33, "text": "     ", "clue": null },
    { "seq": 34, "text": "         ", "clue": null },
    { "seq": 36, "text": "   ", "clue": null },
    { "seq": 37, "text": "    ", "clue": null },
    { "seq": 39, "text": "   ", "clue": null },
    { "seq": 43, "text": "    ", "clue": null },
    { "seq": 44, "text": "       ", "clue": null },
    { "seq": 45, "text": "       ", "clue": null },
    { "seq": 46, "text": "    ", "clue": null },
    { "seq": 50, "text": "      ", "clue": null },
    { "seq": 53, "text": "     ", "clue": null },
    { "seq": 54, "text": "     ", "clue": null },
    { "seq": 56, "text": "     ", "clue": null },
    { "seq": 57, "text": "     ", "clue": null },
    { "seq": 58, "text": "     ", "clue": null },
    { "seq": 60, "text": "    ", "clue": null },
    { "seq": 61, "text": "    ", "clue": null },
    { "seq": 62, "text": "   ", "clue": null },
    { "seq": 63, "text": "   ", "clue": null }
  ]
}'''
        puzzle = Puzzle.from_json(jsonstr)
        return puzzle
예제 #17
0
def puzzle_click(direction):
    """ Common method used by both puzzle_click_across and puzzle_click_down """

    # Get the seq or row and column clicked from the query parms
    r = request.args.get('r', None)
    c = request.args.get('c', None)
    seq = request.args.get('seq', None)

    # Get the existing puzzle from the session
    puzzle = Puzzle.from_json(session['puzzle'])
    puzzlename = session.get('puzzlename', None)
    puzzletitle = puzzle.title

    if r is not None and c is not None:
        r = int(r)
        c = int(c)
        # Get the numbered cell at (r, c)
        numbered_cell = puzzle.get_numbered_cell(r, c)
        if not numbered_cell:
            errmsg = f"({r},{c}) is not a numbered cell"
            return redirect(url_for('uipuzzle.puzzle_screen'))
        seq = numbered_cell.seq
    elif seq is not None:
        seq = int(seq)

    # Get the word
    if direction.startswith('A'):
        word = puzzle.get_across_word(seq)
        if not word:
            errmsg = f"Not the start of an across word"
            return redirect(url_for('uipuzzle.puzzle_screen'))
    else:
        word = puzzle.get_down_word(seq)
        if not word:
            errmsg = f"Not the start of a down word"
            return redirect(url_for('uipuzzle.puzzle_screen'))
        pass

    length = word.length

    # Save seq, direction, and length in the session
    session['seq'] = seq
    session['direction'] = direction
    session['length'] = length

    # Get the existing text and clue from this word
    # and replace blanks with "."
    text = word.get_text()
    text = re.sub(' ', '.', text)
    clue = word.get_clue()
    if not clue:
        clue = ""

    # Create the SVG
    svg = PuzzleToSVG(puzzle)
    svgstr = svg.generate_xml()

    # Invoke the puzzle word editor
    session['uistate'] = UIState.EDITING_WORD
    enabled = session['uistate'].get_enabled()

    return render_template("word-edit.html",
                           enabled=enabled,
                           puzzlename=puzzlename,
                           puzzletitle=puzzletitle,
                           svgstr=svgstr,
                           seq=seq,
                           direction=direction,
                           text=text,
                           clue=clue,
                           length=length)
예제 #18
0
 def run_json(self):
     """ Creates puzzle from JSON input """
     return Puzzle.from_json(self.data)
예제 #19
0
    def create_test_puzzle():
        jsonstr = """{
  "n": 9,
  "cells": [
    "+-----------------+",
    "|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| |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| |",
    "|R|O|S|S|*|*|E|R|E|",
    "+-----------------+"
  ],
  "black_cells": [
    [ 1, 4 ],
    [ 1, 5 ],
    [ 2, 5 ],
    [ 4, 9 ],
    [ 5, 1 ],
    [ 5, 2 ],
    [ 5, 8 ],
    [ 5, 9 ],
    [ 6, 1 ],
    [ 8, 5 ],
    [ 9, 5 ],
    [ 9, 6 ]
  ],
  "numbered_cells": [
    { "seq": 1, "r": 1, "c": 1, "a": 3, "d": 4 },
    { "seq": 2, "r": 1, "c": 2, "a": 0, "d": 4 },
    { "seq": 3, "r": 1, "c": 3, "a": 0, "d": 9 },
    { "seq": 4, "r": 1, "c": 6, "a": 4, "d": 8 },
    { "seq": 5, "r": 1, "c": 7, "a": 0, "d": 9 },
    { "seq": 6, "r": 1, "c": 8, "a": 0, "d": 4 },
    { "seq": 7, "r": 1, "c": 9, "a": 0, "d": 3 },
    { "seq": 8, "r": 2, "c": 1, "a": 4, "d": 0 },
    { "seq": 9, "r": 2, "c": 4, "a": 0, "d": 8 },
    { "seq": 10, "r": 2, "c": 6, "a": 4, "d": 0 },
    { "seq": 11, "r": 3, "c": 1, "a": 9, "d": 0 },
    { "seq": 12, "r": 3, "c": 5, "a": 0, "d": 5 },
    { "seq": 13, "r": 4, "c": 1, "a": 8, "d": 0 },
    { "seq": 14, "r": 5, "c": 3, "a": 5, "d": 0 },
    { "seq": 15, "r": 6, "c": 2, "a": 8, "d": 4 },
    { "seq": 16, "r": 6, "c": 8, "a": 0, "d": 4 },
    { "seq": 17, "r": 6, "c": 9, "a": 0, "d": 4 },
    { "seq": 18, "r": 7, "c": 1, "a": 9, "d": 3 },
    { "seq": 19, "r": 8, "c": 1, "a": 4, "d": 0 },
    { "seq": 20, "r": 8, "c": 6, "a": 4, "d": 0 },
    { "seq": 21, "r": 9, "c": 1, "a": 4, "d": 0 },
    { "seq": 22, "r": 9, "c": 7, "a": 3, "d": 0 }
  ],
  "across_words": [
    { "seq": 1, "text": "DAB", "clue": null },
    { "seq": 4, "text": "EFTS", "clue": null },
    { "seq": 8, "text": "SLIM", "clue": null },
    { "seq": 10, "text": "RIOT", "clue": null },
    { "seq": 11, "text": "LOCAVORES", "clue": null },
    { "seq": 13, "text": "REUNI ED", "clue": null },
    { "seq": 14, "text": "RAPID", "clue": null },
    { "seq": 15, "text": "RICECAKE", "clue": null },
    { "seq": 18, "text": "COOLRANCH", "clue": null },
    { "seq": 19, "text": "CLUE", "clue": null },
    { "seq": 20, "text": " CA ", "clue": null },
    { "seq": 21, "text": "ROSS", "clue": null },
    { "seq": 22, "text": "ERE", "clue": null }
  ],
  "down_words": [
    { "seq": 1, "text": "DSLR", "clue": null },
    { "seq": 2, "text": "ALOE", "clue": null },
    { "seq": 3, "text": "BICURIOUS", "clue": null },
    { "seq": 4, "text": "ERO ICA ", "clue": null },
    { "seq": 5, "text": "FIREDANCE", "clue": null },
    { "seq": 6, "text": "TOED", "clue": null },
    { "seq": 7, "text": "STS", "clue": null },
    { "seq": 9, "text": "MANACLES", "clue": null },
    { "seq": 12, "text": "VIPER", "clue": null },
    { "seq": 15, "text": "ROLO", "clue": null },
    { "seq": 16, "text": "KCAR", "clue": null },
    { "seq": 17, "text": "EH E", "clue": null },
    { "seq": 18, "text": "CCR", "clue": null }
  ],
  "undo_stack": [],
  "redo_stack": []
}
"""
        puzzle = Puzzle.from_json(jsonstr)
        return puzzle
 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)