~dvko/chessrepetitions

75d343543f2ff4c0e848d3055d4fa2db4422c5c9 — Danny van Kooten 2 years ago 6c1df08
get rid of refs to /practice and detect logged-out user successfully
M app.py => app.py +3 -9
@@ 84,6 84,7 @@ class Puzzle(db.Model):
    game = db.relationship('Game', backref=db.backref('puzzles', lazy=True))
    user = db.relationship('User', backref=db.backref('puzzles', lazy=True))
    due = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    last_interval = db.Column(db.Integer, default=1)

    def to_dict(self):
        return { 


@@ 115,7 116,6 @@ def allow_cors(response):


@app.route('/')
@app.route('/practice')
def home():
    return render_template("index.html")



@@ 156,14 156,8 @@ def puzzle_get(id):
@app.route("/api/puzzles/<id>", methods=["POST"])
def puzzle_update(id):
    puzzle = Puzzle.query.get(id)

    match request.args.get('bucket'):
        case '1':
            puzzle.due = datetime.utcnow()+ timedelta(days=1)

        case '2':
            puzzle.due = datetime.utcnow() + timedelta(days=7)
   
    puzzle.last_interval = puzzle.last_interval * 2 if request.args.get('correct') == '1' else 1
    puzzle.due = datetime.utcnow()+ timedelta(days=puzzle.last_interval)   
    db.session.commit()
    return jsonify(True)


M puzzles.py => puzzles.py +16 -9
@@ 99,37 99,44 @@ def for_user(username):
        if game is None:
            break

        db.session.add(game)        
        db.session.add(game)    
        db.session.commit()    

    user.last_sync = datetime.utcnow()
    db.session.commit()
    run_engine()
    solve_all_puzzles()
    return game

def solve(p):
    engine = chess.engine.SimpleEngine.popen_uci("stockfish")
    limit = chess.engine.Limit(depth=16)
def solve(p, engine):
    limit = chess.engine.Limit(depth=22)
    board = chess.Board(p.fen)
    results = engine.analyse(board, limit, multipv=5)
    results = engine.analyse(board, limit, multipv=8)
    top = results[0]
    top_score = top['score'].pov(board.turn).score(mate_score=10000)
    score_treshold = top_score - (0.2 * abs(top_score))
    moves = [top['pv'][0].uci()]

    for r in results[1:]:
        move_score = r['score'].pov(board.turn).score(mate_score=10000)
        #print(r['pv'][0].uci(), move_score, classify_move(move_score, top_score))

        if (classify_move(move_score, top_score) >= -0.1):
            moves.append(r['pv'][0].uci())
        else:
            break

    p.move_engine = ','.join(moves)
    engine.quit()
    return p

def run_engine():
def solve_all_puzzles():
    engine = chess.engine.SimpleEngine.popen_uci("stockfish")
    puzzles = Puzzle.query.filter_by(move_engine=None).all()
    for p in puzzles:
        solve(p)
        solve(p, engine)
        db.session.commit()
    db.session.commit()
    engine.quit()


if __name__ == '__main__':
    puzzles()

M test_puzzles.py => test_puzzles.py +19 -11
@@ 1,6 1,7 @@
import puzzles
import chess 

def test_fetch_puzzles():
def test_get_puzzle_by_game_id():
    # https://lichess.org/RsO2gx3n/white
    # Ply's we want as puzzle: 40, 46, 58, 60
    game = puzzles.by_id("dvko", "RsO2gx3n")


@@ 16,32 17,39 @@ def test_fetch_puzzles():
    assert len(game.puzzles) == 1
    assert game.puzzles[0].ply == 9 

def test_run_engine():
def test_solve():
    engine = chess.engine.SimpleEngine.popen_uci("stockfish")

    # https://lichess.org/RsO2gx3n/white
    game = puzzles.by_id("dvko", "RsO2gx3n")

    # Top moves: c1c2 (uci)
    puzzles.solve(game.puzzles[0])
    print("solving https://lichess.org/RsO2gx3n/white#40")
    print("expecting c1c2")
    puzzles.solve(game.puzzles[0], engine)
    assert game.puzzles[0].move_engine is not None
    assert game.puzzles[0].move_engine == "c1c2"

    # Top moves: d1b1, d1f1, h1f1
    puzzles.solve(game.puzzles[1])
    print("solving https://lichess.org/RsO2gx3n/white#46")
    print("expecting d1b1, d1f1, h1f1")
    puzzles.solve(game.puzzles[1], engine)
    moves = game.puzzles[1].move_engine.split(',')
    assert game.puzzles[1].move_engine is not None
    assert len(moves) == 3
    assert sorted(moves) == sorted(["d1f1","d1b1","h1f1"])

    # Top moves: f1f5, d3d2
    puzzles.solve(game.puzzles[2])
    print("solving https://lichess.org/RsO2gx3n/white#58")
    print("expecting f1f5, d3d2")
    puzzles.solve(game.puzzles[2], engine)
    moves = game.puzzles[2].move_engine.split(',')
    assert game.puzzles[2].move_engine is not None
    assert len(moves) == 2
    assert sorted(moves) == sorted(["f1f5", "d3d2"])

    # Top moves: g5f5, f1f5
    puzzles.solve(game.puzzles[3])
    print("solving https://lichess.org/RsO2gx3n/white#60")
    print("Top moves: g5f5, f1f5, f1f6")
    puzzles.solve(game.puzzles[3], engine)
    moves = game.puzzles[3].move_engine.split(',')
    assert game.puzzles[3].move_engine is not None
    assert len(moves) == 3
    assert sorted(moves) == sorted(["g5f5", "f1f5", "f1f6"])

    engine.quit()

M web/src/components/App.css => web/src/components/App.css +23 -1
@@ 29,10 29,32 @@ h1 {
	.result { padding: 0 20px; }
}

.board-wrap {
	background: #262421;
}

.result {
	font-size: 24px;
	font-size: 20px;
	display: flex;
	align-items: center;
}

.muted {
	font-size: 14px;
}

.king {
	flex: 0 0 64px;
	height: 64px;
	margin-right: 10px;
	width: 64px;
	margin-right: 10px;
	background-size: cover;
}

.king.white {
	background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTBtbSIgaGVpZ2h0PSI1MG1tIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIgaW1hZ2UtcmVuZGVyaW5nPSJvcHRpbWl6ZVF1YWxpdHkiIHNoYXBlLXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB0ZXh0LXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiBpbmtzY2FwZTp2ZXJzaW9uPSIxLjAuMSAoM2JjMmU4MTNmNSwgMjAyMC0wOS0wNykiIHNvZGlwb2RpOmRvY25hbWU9IndLLnN2ZyIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogPHNvZGlwb2RpOm5hbWVkdmlldyBib3JkZXJjb2xvcj0iIzY2NjY2NiIgYm9yZGVyb3BhY2l0eT0iMSIgZ3JpZHRvbGVyYW5jZT0iMTAiIGd1aWRldG9sZXJhbmNlPSIxMCIgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnOTEiIGlua3NjYXBlOmN4PSIxMjEuMTU0NDUiIGlua3NjYXBlOmN5PSIxNDAuOTIzOTQiIGlua3NjYXBlOmRvY3VtZW50LXJvdGF0aW9uPSIwIiBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMCIgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iMTAwMSIgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxOTIwIiBpbmtzY2FwZTp3aW5kb3cteD0iLTkiIGlua3NjYXBlOndpbmRvdy15PSItOSIgaW5rc2NhcGU6em9vbT0iMS41NTI4MzYiIG9iamVjdHRvbGVyYW5jZT0iMTAiIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIgc2hvd2dyaWQ9ImZhbHNlIi8+CiA8ZGVmcz4KICA8bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhckdyYWRpZW50Mjc1OCIgeDE9Ii01MDUuOTciIHgyPSItNDg0LjIyIiB5MT0iLTQwOC41IiB5Mj0iLTQwOC41IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEuMDExMyAwIDAgMS4wMDA4IDUzNi4yMiA0MzMuNzkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeGxpbms6aHJlZj0iI2xpbmVhckdyYWRpZW50MTY0MyIvPgogIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyR3JhZGllbnQxNjQzIiB4MT0iOS4yNDA3IiB4Mj0iNDAuNzYxIiB5MT0iMjcuMjY2IiB5Mj0iMjcuMjY2IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC45ODQ5NSAwIDAgLjk4NjA1IC4zNzU1OSAuNjQxMTkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgIDxzdG9wIHN0b3AtY29sb3I9IiNlY2U5ZGYiIG9mZnNldD0iMCIvPgogICA8c3RvcCBzdG9wLWNvbG9yPSIjZjRlMGM4IiBvZmZzZXQ9IjEiLz4KICA8L2xpbmVhckdyYWRpZW50PgogIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyR3JhZGllbnQyNzYwIiB4MT0iLTUyMC4xNSIgeDI9Ii00OTAuODQiIHkxPSItMzk0LjQ0IiB5Mj0iLTM5NC40NCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLjAxMTMgMCAwIDEuMDAwOCA1MzYuMjIgNDMzLjc5KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHhsaW5rOmhyZWY9IiNsaW5lYXJHcmFkaWVudDE2NDMiLz4KICA8bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhckdyYWRpZW50Mjc2MiIgeDE9Ii01MjYuNzQiIHgyPSItNTA0Ljk4IiB5MT0iLTQwOC41IiB5Mj0iLTQwOC41IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEuMDExMyAwIDAgMS4wMDA4IDUzNi4yMiA0MzMuNzkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeGxpbms6aHJlZj0iI2xpbmVhckdyYWRpZW50MTY0MyIvPgogIDxmaWx0ZXIgaWQ9ImZpbHRlcjE2NDQtMiIgeD0iLS4wODQ3NTkiIHk9Ii0uMDMzMzc1IiB3aWR0aD0iMS4xNjk1IiBoZWlnaHQ9IjEuMDY2NyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KICAgPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMC4zOTE4MTU0MyIvPgogIDwvZmlsdGVyPgogIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyR3JhZGllbnQyNzY0IiB4MT0iLTUxMC4wOCIgeDI9Ii01MDAuODUiIHkxPSItNDEyLjcyIiB5Mj0iLTQxMi43MiIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLjAxMTMgMCAwIDEuMDAwOCA1MzYuMjIgNDMzLjc5KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHhsaW5rOmhyZWY9IiNsaW5lYXJHcmFkaWVudDE2NDMiLz4KICA8ZmlsdGVyIGlkPSJmaWx0ZXIxODk0LTEiIHg9Ii0uMTAyMzIiIHk9Ii0uMDMxMjQxIiB3aWR0aD0iMS4yMDQ2IiBoZWlnaHQ9IjEuMDYyNSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KICAgPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMC4xNzIxNDA4MiIvPgogIDwvZmlsdGVyPgogIDxmaWx0ZXIgaWQ9ImZpbHRlcjE4OTgtMCIgeD0iLS4wNTgyNzEiIHk9Ii0uMDQwNzQ0IiB3aWR0aD0iMS4xMTY1IiBoZWlnaHQ9IjEuMDgxNSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KICAgPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMC4yNTc3NjE0MiIvPgogIDwvZmlsdGVyPgogIDxmaWx0ZXIgaWQ9ImZpbHRlcjE2NDQtMi0zLTYiIHg9Ii0uMDg0NzU5IiB5PSItLjAzMzM3NSIgd2lkdGg9IjEuMTY5NSIgaGVpZ2h0PSIxLjA2NjciIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CiAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuMzkxODE1NDMiLz4KICA8L2ZpbHRlcj4KICA8ZmlsdGVyIGlkPSJmaWx0ZXIxODk0LTEtNS02IiB4PSItLjEwMjMyIiB5PSItLjAzMTI0MSIgd2lkdGg9IjEuMjA0NiIgaGVpZ2h0PSIxLjA2MjUiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CiAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuMTcyMTQwODIiLz4KICA8L2ZpbHRlcj4KICA8ZmlsdGVyIGlkPSJmaWx0ZXIxODk4LTAtNC01IiB4PSItLjA1ODI3MSIgeT0iLS4wNDA3NDQiIHdpZHRoPSIxLjExNjUiIGhlaWdodD0iMS4wODE1IiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgogICA8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIwLjI1Nzc2MTQyIi8+CiAgPC9maWx0ZXI+CiA8L2RlZnM+CiA8cGF0aCBkPSJtMjkuMTMyIDE4Ljc5MmM2LjM4NjgtNS43NDA5IDE3LjU0NC0yLjYwNjMgMTYuODUxIDYuODEyNS0wLjY3ODg0IDYuMTcwNC03LjAxMzIgOC4zNDc0LTcuMDEzMiA4LjM0NzRzLTMuODI3LTIuMjI3OC0xMy45NC0yLjIyNzlsLTAuMDEzNzUtMy45OTI5eiIgZmlsbD0idXJsKCNsaW5lYXJHcmFkaWVudDI3NTgpIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsOnVybCgjbGluZWFyR3JhZGllbnQyNzU4KTtpbWFnZS1yZW5kZXJpbmc6b3B0aW1pemVRdWFsaXR5O3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb247c3Ryb2tlOiMwMDAwMDAiLz4KIDxwYXRoIGQ9Im0zNy45NDIgMzguODMxIDEuMzA0NCA1LjI3NThzLTMuNzc4OCAyLjIyNzktMTQuMjQ3IDIuMjI4MWMtMTAuNDY4LTJlLTQgLTE0LjI0Ny0yLjIyODEtMTQuMjQ3LTIuMjI4MWwxLjMwMzktNS4yNzU4LTAuOTk2NTMtNC44Nzg1czMuNjQ1Mi0yLjIyOCAxMy45NDEtMi4yMjgxYzEwLjI5NS05ZS01IDEzLjkzOSAyLjIyODEgMTMuOTM5IDIuMjI4MXoiIGZpbGw9InVybCgjbGluZWFyR3JhZGllbnQyNzYwKSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDp1cmwoI2xpbmVhckdyYWRpZW50Mjc2MCk7aW1hZ2UtcmVuZGVyaW5nOm9wdGltaXplUXVhbGl0eTtzaGFwZS1yZW5kZXJpbmc6Z2VvbWV0cmljUHJlY2lzaW9uO3N0cm9rZTojMDAwMDAwIi8+CiA8cGF0aCBkPSJtMTAuNzU0IDQ0LjEwNnMzLjc3OS0yLjIyODIgMTQuMjQ4LTIuMjI4MmMxMC40NjktOGUtNSAxNC4yNDggMi4yMjgyIDE0LjI0OCAyLjIyODIiIGZpbGw9Im5vbmUiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOmV2ZW5vZGQ7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbjtzdHJva2U6IzAwMDAwMCIvPgogPHBhdGggZD0ibTEyLjA1OCAzOC44MzFzMy4zOTM3LTIuMjI4MSAxMi45NzgtMi4yMjgyYzkuNTgzNy05ZS01IDEyLjk3NyAyLjIyODIgMTIuOTc3IDIuMjI4MiIgZmlsbD0ibm9uZSIgaW1hZ2UtcmVuZGVyaW5nPSJvcHRpbWl6ZVF1YWxpdHkiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtzaGFwZS1yZW5kZXJpbmc6Z2VvbWV0cmljUHJlY2lzaW9uO3N0cm9rZTojMDAwMDAwIi8+CiA8cGF0aCBkPSJtMjAuOSAxOC43OTJjLTYuMzg2OC01Ljc0MDktMTcuNTQ0LTIuNjA2My0xNi44NTEgNi44MTI1IDAuNjc4ODMgNi4xNzA1IDcuMDEzMiA4LjM0NzQgNy4wMTMyIDguMzQ3NHMzLjgyNy0yLjIyNzggMTMuOTQtMi4yMjc5bDAuMDEzNzUtMy45OTI5eiIgZmlsbD0idXJsKCNsaW5lYXJHcmFkaWVudDI3NjIpIiBzdHJva2U9IiMwMDAwMDAiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtmaWxsOnVybCgjbGluZWFyR3JhZGllbnQyNzYyKTtpbWFnZS1yZW5kZXJpbmc6b3B0aW1pemVRdWFsaXR5O3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb24iLz4KIDxwYXRoIGQ9Im0yMS42NSA5LjkxMTFoNi43MzI3bS0zLjM2NjUtMy41ODI1djcuOTgxMSIgZmlsbD0iIzU5OTE3YSIgaW1hZ2UtcmVuZGVyaW5nPSJvcHRpbWl6ZVF1YWxpdHkiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOmV2ZW5vZGQ7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggdHJhbnNmb3JtPSJtYXRyaXgoMS4wMTEzIDAgMCAxLjAwMDggNTM2LjIyIDQzMy43OSkiIGQ9Im0tNDk0LjA4LTQxNy41M2MtMC41NTUyNCA0ZS0zIC0xLjEzMjggMC4wNTEyLTEuNzMxNyAwLjE0NTUxIDkuMDg3NS0wLjIyMzUxIDEzLjQ0MyAxMS45NTggMS4zOTM5IDE2LjQ4NWwtMS4yNzIgNC45NTg0IDEuNTI1OCA1LjQ4MjEgMi45NjEzIDEuMTA0Ni0xLjQ4OTItNS40Njg1IDAuOTg2Ni01LjIwNjlzNi4yNzI5LTEuNzgxNyA2LjkzNDEtNy44MjFjMC41MDYyNi00LjYyMzctMi41NDcxLTkuNzI2Ny05LjMwODgtOS42NzkxeiIgZmlsdGVyPSJ1cmwoI2ZpbHRlcjE2NDQtMikiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBvcGFjaXR5PSIuMjUiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtmaWx0ZXI6dXJsKCNmaWx0ZXIxNjQ0LTItMy02KTttaXgtYmxlbmQtbW9kZTpub3JtYWw7b3BhY2l0eTouMTU7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggZD0ibTIwLjkgMTguNzkyIDQuMTI5NiA4LjY1MzQgNC4xMjk2LTguNjUzNHMwLjcwMzI2LTQuNzY3NS00LjEyOTYtNC43Njc1Yy00LjgzMjggMC00LjEyOTYgNC43Njc1LTQuMTI5NiA0Ljc2NzV6IiBmaWxsPSJ1cmwoI2xpbmVhckdyYWRpZW50Mjc2NCkiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtmaWxsOnVybCgjbGluZWFyR3JhZGllbnQyNzY0KTtzaGFwZS1yZW5kZXJpbmc6Z2VvbWV0cmljUHJlY2lzaW9uIi8+CiA8cGF0aCB0cmFuc2Zvcm09Im1hdHJpeCgxLjAxMTMgMCAwIDEuMDAwOCA1MzYuMjIgNDMzLjc5KSIgZD0ibS01MDUuNDgtNDE5LjJjNC43NTc0IDAuOTcwMDIgMC44NjczNCAxMC41MiAwLjAxNTkgMTMuMTg2IDAuMDI0IDAuMDI1MSAzLjA3ODQtNS40MDM3IDQuMDIwMy04LjkxMjkgMC4wODkxLTQuNTA0Ni0zLjg2NzQtNC4zOTI2LTQuMDM2Mi00LjI3MzR6IiBmaWx0ZXI9InVybCgjZmlsdGVyMTg5NC0xKSIgaW1hZ2UtcmVuZGVyaW5nPSJvcHRpbWl6ZVF1YWxpdHkiIG9wYWNpdHk9Ii4yNSIgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGwtcnVsZTpldmVub2RkO2ZpbHRlcjp1cmwoI2ZpbHRlcjE4OTQtMS01LTYpO21peC1ibGVuZC1tb2RlOm5vcm1hbDtvcGFjaXR5Oi4xNTtzaGFwZS1yZW5kZXJpbmc6Z2VvbWV0cmljUHJlY2lzaW9uIi8+CiA8cGF0aCB0cmFuc2Zvcm09Im1hdHJpeCgxLjAxMTMgMCAwIDEuMDAwOCA1MzYuMjIgNDMzLjc5KSIgZD0ibS01MDUuNDktNDAyLjI2LTkuMWUtNCAtMC4wNzUzYzkuMWUtNCAwLjA3NTMgMC4wMTU2LTMuMzk1MyAwLjAxNTYtMy4zOTUzcy0yLjY4MDktNi44OTU2LTQuMDUxNi04LjkxNDFjLTEuMzMzNS0xLjk2MzctMy43NzgyLTIuOTI5NS02LjU2NDgtMi43ODQzIDMuOTcyMiAxLjc2NTEgOC45MzcyIDEwLjAyOSAxMC42MDIgMTUuMTY5eiIgZmlsdGVyPSJ1cmwoI2ZpbHRlcjE4OTgtMCkiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBvcGFjaXR5PSIuMjUiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtmaWx0ZXI6dXJsKCNmaWx0ZXIxODk4LTAtNC01KTttaXgtYmxlbmQtbW9kZTpub3JtYWw7b3BhY2l0eTouMTU7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggZD0ibTYuMzE0MyAyOS41MjdjLTEuMTk2Mi0xLjY0NzMtMi4wMTEyLTMuODUwMS0xLjQ0NTctNi42MzU1IDEuNDM2NS03LjA3NDUgOS40NTctNi4yOTYzIDkuNDU3LTYuMjk2My0xMS41OTUgMy40Njc5LTcuODcxOSAxMi45NjItOC4wMTEzIDEyLjkzMnoiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtmaWxsOiNmZmZmZmY7aW1hZ2UtcmVuZGVyaW5nOm9wdGltaXplUXVhbGl0eTtvcGFjaXR5Oi44O3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb24iLz4KIDxwYXRoIGQ9Im0yMy41NyAyMy4wMjUtMi4xMTM1LTQuNDAyNHMtMC42OTA1NS00LjExOTkgMy41NDAyLTQuMDU2Yy00LjIxMyAxLjMzMS0xLjQyNjcgOC40NTg1LTEuNDI2NyA4LjQ1ODV6IiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOmV2ZW5vZGQ7ZmlsbDojZmZmZmZmO2ltYWdlLXJlbmRlcmluZzpvcHRpbWl6ZVF1YWxpdHk7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggZD0ibTI3LjAyNiAyNC45NTkgMi43NTIxLTUuOTI4N3MyLjQ0NjYtMi4yMjM2IDYuMTE2NC0yLjU3MTVjLTMuOTQ3NSAxLjAzMjEtNi4zMzAxIDQuODc0Ny04Ljg2ODUgOC41MDAzeiIgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGwtcnVsZTpldmVub2RkO2ZpbGw6I2ZmZmZmZjtpbWFnZS1yZW5kZXJpbmc6b3B0aW1pemVRdWFsaXR5O3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb24iLz4KIDxwYXRoIGQ9Im0yNS4zMTggNDEuOTI0Yy0xMC4zMTEgMWUtNCAtMTQuNTY0IDIuMTgyNy0xNC41NjQgMi4xODI3czQuMjUyOSAyLjE4MjUgMTQuNTY0IDIuMTgyNWgwLjAyMTI0Yy0xNS40NDEtMS45OSA3LjMxNDgtMy44MDMgOC4yODQyLTMuODEwNy0yLjE3MzMtMC4zMTY0Mi00LjkxNzEtMC41NTM4My04LjMwNTItMC41NTM3M3oiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBvcGFjaXR5PSIuMiIgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGwtcnVsZTpldmVub2RkO29wYWNpdHk6LjE1O3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb24iLz4KIDxwYXRoIGQ9Im0yNSA0Ni4yOTZjLTkuMjQxIDhlLTUgLTEzLjc0NC0yLjIwODgtMTMuNzQ0LTIuMjA4OHM0LjUwMy0yLjIwODcgMTMuNzQ0LTIuMjA4OGM5LjI0MS04ZS01IDEzLjc0NCAyLjIwODggMTMuNzQ0IDIuMjA4OHMtNC41MDMgMi4yMDg3LTEzLjc0NCAyLjIwODh6IiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgb3BhY2l0eT0iLjIiIHN0cm9rZT0iIzAwMDAwMCIgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGwtcnVsZTpldmVub2RkO29wYWNpdHk6LjE1O3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb247c3Ryb2tlLXdpZHRoOjAiLz4KIDxwYXRoIGQ9Im0xMi41MjggMzkuMTY5YzAuODUwMTQtMC40NDk3MiAxLjcyODgtMC42NDI4NiAyLjU5MzQtMC45NjE5MS0wLjU4Nzg2IDAuODQwOTYtMC42MzQ0IDIuNzIzOS0wLjM1NzIzIDQuMDYyMyAwIDAtMC44OTIwMSAwLjEyNDIzLTMuMjMxOCAwLjkwNDI5eiIgZmlsbD0iI2ZmZmZmZiIgaW1hZ2UtcmVuZGVyaW5nPSJvcHRpbWl6ZVF1YWxpdHkiIG9wYWNpdHk9Ii43IiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOmV2ZW5vZGQ7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggZD0ibTEyLjQ2NCAzOC4wMjFzMC4zMDA2MS0wLjI4Nzc2IDIuNDE2Mi0wLjg3NjgzYy0xLjU5MS0xLjYwMTItMS40MDAyLTMuNDQ2Mi0xLjU3NDctMy41MTYtMC41NjY1IDAuMTY3OS0xLjExMTggMC4zODg5My0xLjY1IDAuNjI3NHoiIGZpbGw9IiNmZmZmZmYiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBvcGFjaXR5PSIuOSIgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGwtcnVsZTpldmVub2RkO29wYWNpdHk6Ljg7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2NjYyIvPgo8L3N2Zz4K')}

.king.black {
	background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTBtbSIgaGVpZ2h0PSI1MG1tIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIgaW1hZ2UtcmVuZGVyaW5nPSJvcHRpbWl6ZVF1YWxpdHkiIHNoYXBlLXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB0ZXh0LXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiBpbmtzY2FwZTp2ZXJzaW9uPSIxLjAuMSAoM2JjMmU4MTNmNSwgMjAyMC0wOS0wNykiIHNvZGlwb2RpOmRvY25hbWU9ImJLLnN2ZyIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogPHNvZGlwb2RpOm5hbWVkdmlldyBib3JkZXJjb2xvcj0iIzY2NjY2NiIgYm9yZGVyb3BhY2l0eT0iMSIgZ3JpZHRvbGVyYW5jZT0iMTAiIGd1aWRldG9sZXJhbmNlPSIxMCIgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnMTAyIiBpbmtzY2FwZTpjeD0iNTYuMjIwODQzIiBpbmtzY2FwZTpjeT0iMTU1LjQ3Njc1IiBpbmtzY2FwZTpkb2N1bWVudC1yb3RhdGlvbj0iMCIgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAiIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjEwMDEiIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjEiIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIgaW5rc2NhcGU6d2luZG93LXg9Ii05IiBpbmtzY2FwZTp3aW5kb3cteT0iLTkiIGlua3NjYXBlOnpvb209IjEuMDk4MDIwOCIgb2JqZWN0dG9sZXJhbmNlPSIxMCIgcGFnZWNvbG9yPSIjZmZmZmZmIiBzaG93Z3JpZD0iZmFsc2UiLz4KIDxkZWZzPgogIDxmaWx0ZXIgaWQ9ImZpbHRlcjE4OTQtMSIgeD0iLS4xMDIzMiIgeT0iLS4wMzEyNDEiIHdpZHRoPSIxLjIwNDYiIGhlaWdodD0iMS4wNjI1IiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgogICA8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIwLjE3MjE0MDgyIi8+CiAgPC9maWx0ZXI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXJHcmFkaWVudDI0MDMiIHgxPSI5LjI0MDciIHgyPSI0MC43NjEiIHkxPSIyNy4yNjYiIHkyPSIyNy4yNjYiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMS4wMTU1IDAgMCAxLjAxMDMgLS4zODg1MiAuNDgxNTMpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgIDxzdG9wIHN0b3AtY29sb3I9IiM2MzVmNWUiIHN0eWxlPSJzdG9wLWNvbG9yOiM3MzczNzMiIG9mZnNldD0iMCIvPgogICA8c3RvcCBzdG9wLWNvbG9yPSIjMTMxMTExIiBzdHlsZT0ic3RvcC1jb2xvcjojMzAzMDMwIiBvZmZzZXQ9IjEiLz4KICA8L2xpbmVhckdyYWRpZW50PgogIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyR3JhZGllbnQyMzY1IiB4MT0iLTUwNS45NyIgeDI9Ii00ODQuMjIiIHkxPSItNDA4LjUiIHkyPSItNDA4LjUiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMS4wMTEzIDAgMCAxLjAwMDggNTM2LjIyIDQzMy43OSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMiIHhsaW5rOmhyZWY9IiNsaW5lYXJHcmFkaWVudDI0MDMiLz4KICA8bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhckdyYWRpZW50MjM2NyIgeDE9Ii01MjAuMTUiIHgyPSItNDkwLjg0IiB5MT0iLTM5NC40NCIgeTI9Ii0zOTQuNDQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMS4wMTEzIDAgMCAxLjAwMDggNTM2LjIyIDQzMy43OSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMiIHhsaW5rOmhyZWY9IiNsaW5lYXJHcmFkaWVudDI0MDMiLz4KICA8bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhckdyYWRpZW50MjM2OSIgeDE9Ii01MjYuNzQiIHgyPSItNTA0Ljk4IiB5MT0iLTQwOC41IiB5Mj0iLTQwOC41IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEuMDExMyAwIDAgMS4wMDA4IDUzNi4yMiA0MzMuNzkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgaW5rc2NhcGU6Y29sbGVjdD0iYWx3YXlzIiB4bGluazpocmVmPSIjbGluZWFyR3JhZGllbnQyNDAzIi8+CiAgPGZpbHRlciBpZD0iZmlsdGVyMTY0NC0yLTMtOS01IiB4PSItLjA4NDc1OSIgeT0iLS4wMzMzNzUiIHdpZHRoPSIxLjE2OTUiIGhlaWdodD0iMS4wNjY3IiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgogICA8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIwLjM5MTgxNTQzIi8+CiAgPC9maWx0ZXI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXJHcmFkaWVudDIzNzEiIHgxPSItNTEwLjA4IiB4Mj0iLTUwMC44NSIgeTE9Ii00MTIuNzIiIHkyPSItNDEyLjcyIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEuMDExMyAwIDAgMS4wMDA4IDUzNi4yMiA0MzMuNzkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgaW5rc2NhcGU6Y29sbGVjdD0iYWx3YXlzIiB4bGluazpocmVmPSIjbGluZWFyR3JhZGllbnQyNDAzIi8+CiAgPGZpbHRlciBpZD0iZmlsdGVyMTg5NC0xLTUtNS0yIiB4PSItLjEwMjMyIiB5PSItLjAzMTI0MSIgd2lkdGg9IjEuMjA0NiIgaGVpZ2h0PSIxLjA2MjUiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CiAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuMTcyMTQwODIiLz4KICA8L2ZpbHRlcj4KICA8ZmlsdGVyIGlkPSJmaWx0ZXIxODk4LTAtNC0xLTkiIHg9Ii0uMDU4MjcxIiB5PSItLjA0MDc0NCIgd2lkdGg9IjEuMTE2NSIgaGVpZ2h0PSIxLjA4MTUiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CiAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuMjU3NzYxNDIiLz4KICA8L2ZpbHRlcj4KICA8ZmlsdGVyIGlkPSJmaWx0ZXIyMzg1IiB4PSItLjEyNjU4IiB5PSItLjA5NDE3NyIgd2lkdGg9IjEuMjUzMiIgaGVpZ2h0PSIxLjE4ODQiIHN0eWxlPSJjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM6c1JHQiIgaW5rc2NhcGU6Y29sbGVjdD0iYWx3YXlzIj4KICAgPGZlR2F1c3NpYW5CbHVyIGlua3NjYXBlOmNvbGxlY3Q9ImFsd2F5cyIgc3RkRGV2aWF0aW9uPSIwLjUwODQ1MTA4Ii8+CiAgPC9maWx0ZXI+CiAgPGZpbHRlciBpZD0iZmlsdGVyMjM5MyIgeD0iLS4xODE1MyIgeT0iLS4wNzY4NjYiIHdpZHRoPSIxLjM2MzEiIGhlaWdodD0iMS4xNTM3IiBzdHlsZT0iY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzOnNSR0IiIGlua3NjYXBlOmNvbGxlY3Q9ImFsd2F5cyI+CiAgIDxmZUdhdXNzaWFuQmx1ciBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMiIHN0ZERldmlhdGlvbj0iMC4yNzA5MjgzNiIvPgogIDwvZmlsdGVyPgogIDxmaWx0ZXIgaWQ9ImZpbHRlcjIzODkiIHg9Ii0uMTA1NzYiIHk9Ii0uMTEwMzQiIHdpZHRoPSIxLjIxMTUiIGhlaWdodD0iMS4yMjA3IiBzdHlsZT0iY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzOnNSR0IiIGlua3NjYXBlOmNvbGxlY3Q9ImFsd2F5cyI+CiAgIDxmZUdhdXNzaWFuQmx1ciBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMiIHN0ZERldmlhdGlvbj0iMC4zOTA3OTgiLz4KICA8L2ZpbHRlcj4KICA8ZmlsdGVyIGlkPSJmaWx0ZXIyMzc3IiB4PSItLjIyODg1IiB5PSItLjE2NTM3IiB3aWR0aD0iMS40NTc3IiBoZWlnaHQ9IjEuMzMwNyIgc3R5bGU9ImNvbG9yLWludGVycG9sYXRpb24tZmlsdGVyczpzUkdCIiBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMiPgogICA8ZmVHYXVzc2lhbkJsdXIgaW5rc2NhcGU6Y29sbGVjdD0iYWx3YXlzIiBzdGREZXZpYXRpb249IjAuMzQyMjI0OCIvPgogIDwvZmlsdGVyPgogIDxmaWx0ZXIgaWQ9ImZpbHRlcjIzNzMiIHg9Ii0uMjI2NzgiIHk9Ii0uMTY2NDciIHdpZHRoPSIxLjQ1MzYiIGhlaWdodD0iMS4zMzI5IiBzdHlsZT0iY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzOnNSR0IiIGlua3NjYXBlOmNvbGxlY3Q9ImFsd2F5cyI+CiAgIDxmZUdhdXNzaWFuQmx1ciBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMiIHN0ZERldmlhdGlvbj0iMC4zMDQ3MDEyIi8+CiAgPC9maWx0ZXI+CiA8L2RlZnM+CiA8cGF0aCBkPSJtMjkuMTMyIDE4Ljc5MmM2LjM4NjgtNS43NDA5IDE3LjU0NC0yLjYwNjMgMTYuODUxIDYuODEyNS0wLjY3ODg0IDYuMTcwNC03LjAxMzIgOC4zNDc0LTcuMDEzMiA4LjM0NzRzLTMuODI3LTIuMjI3OC0xMy45NC0yLjIyNzlsLTAuMDEzNzUtMy45OTI5eiIgZmlsbD0idXJsKCNsaW5lYXJHcmFkaWVudDI3NTgpIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsOnVybCgjbGluZWFyR3JhZGllbnQyMzY1KTtpbWFnZS1yZW5kZXJpbmc6b3B0aW1pemVRdWFsaXR5O3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb247c3Ryb2tlOiMwMDAwMDAiLz4KIDxwYXRoIGQ9Im0zNy45NDIgMzguODMxIDEuMzA0NCA1LjI3NThzLTMuNzc4OCAyLjIyNzktMTQuMjQ3IDIuMjI4MWMtMTAuNDY4LTJlLTQgLTE0LjI0Ny0yLjIyODEtMTQuMjQ3LTIuMjI4MWwxLjMwMzktNS4yNzU4LTAuOTk2NTMtNC44Nzg1czMuNjQ1Mi0yLjIyOCAxMy45NDEtMi4yMjgxYzEwLjI5NS05ZS01IDEzLjkzOSAyLjIyODEgMTMuOTM5IDIuMjI4MXoiIGZpbGw9InVybCgjbGluZWFyR3JhZGllbnQyNzYwKSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDp1cmwoI2xpbmVhckdyYWRpZW50MjM2Nyk7aW1hZ2UtcmVuZGVyaW5nOm9wdGltaXplUXVhbGl0eTtzaGFwZS1yZW5kZXJpbmc6Z2VvbWV0cmljUHJlY2lzaW9uO3N0cm9rZTojMDAwMDAwIi8+CiA8cGF0aCBkPSJtMTAuNzU0IDQ0LjEwNnMzLjc3OS0yLjIyODIgMTQuMjQ4LTIuMjI4MmMxMC40NjktOGUtNSAxNC4yNDggMi4yMjgyIDE0LjI0OCAyLjIyODIiIGZpbGw9Im5vbmUiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOmV2ZW5vZGQ7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbjtzdHJva2U6IzAwMDAwMCIvPgogPHBhdGggZD0ibTEyLjA1OCAzOC44MzFzMy4zOTM3LTIuMjI4MSAxMi45NzgtMi4yMjgyYzkuNTgzNy05ZS01IDEyLjk3NyAyLjIyODIgMTIuOTc3IDIuMjI4MiIgZmlsbD0ibm9uZSIgaW1hZ2UtcmVuZGVyaW5nPSJvcHRpbWl6ZVF1YWxpdHkiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtzaGFwZS1yZW5kZXJpbmc6Z2VvbWV0cmljUHJlY2lzaW9uO3N0cm9rZTojMDAwMDAwIi8+CiA8cGF0aCBkPSJtMjAuOSAxOC43OTJjLTYuMzg2OC01Ljc0MDktMTcuNTQ0LTIuNjA2My0xNi44NTEgNi44MTI1IDAuNjc4ODMgNi4xNzA1IDcuMDEzMiA4LjM0NzQgNy4wMTMyIDguMzQ3NHMzLjgyNy0yLjIyNzggMTMuOTQtMi4yMjc5bDAuMDEzNzUtMy45OTI5eiIgZmlsbD0idXJsKCNsaW5lYXJHcmFkaWVudDI3NjIpIiBzdHJva2U9IiMwMDAwMDAiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtmaWxsOnVybCgjbGluZWFyR3JhZGllbnQyMzY5KTtpbWFnZS1yZW5kZXJpbmc6b3B0aW1pemVRdWFsaXR5O3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb24iLz4KIDxwYXRoIGQ9Im0yMS42NSA5LjkxMTFoNi43MzI3bS0zLjM2NjUtMy41ODI1djcuOTgxMSIgZmlsbD0iIzU5OTE3YSIgaW1hZ2UtcmVuZGVyaW5nPSJvcHRpbWl6ZVF1YWxpdHkiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOmV2ZW5vZGQ7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggdHJhbnNmb3JtPSJtYXRyaXgoMS4wMTEzIDAgMCAxLjAwMDggNTM2LjIyIDQzMy43OSkiIGQ9Im0tNDk0LjA4LTQxNy41M2MtMC41NTUyNCA0ZS0zIC0xLjEzMjggMC4wNTEyLTEuNzMxNyAwLjE0NTUxIDkuMDg3NS0wLjIyMzUxIDEzLjQ0MyAxMS45NTggMS4zOTM5IDE2LjQ4NWwtMS4yNzIgNC45NTg0IDEuNTI1OCA1LjQ4MjEgMi45NjEzIDEuMTA0Ni0xLjQ4OTItNS40Njg1IDAuOTg2Ni01LjIwNjlzNi4yNzI5LTEuNzgxNyA2LjkzNDEtNy44MjFjMC41MDYyNi00LjYyMzctMi41NDcxLTkuNzI2Ny05LjMwODgtOS42NzkxeiIgZmlsdGVyPSJ1cmwoI2ZpbHRlcjE2NDQtMikiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBvcGFjaXR5PSIuMjUiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtmaWx0ZXI6dXJsKCNmaWx0ZXIxNjQ0LTItMy05LTUpO21peC1ibGVuZC1tb2RlOm5vcm1hbDtvcGFjaXR5Oi4yO3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb24iLz4KIDxwYXRoIGQ9Im0yMC45IDE4Ljc5MiA0LjEyOTYgOC42NTM0IDQuMTI5Ni04LjY1MzRzMC43MDMyNi00Ljc2NzUtNC4xMjk2LTQuNzY3NWMtNC44MzI4IDAtNC4xMjk2IDQuNzY3NS00LjEyOTYgNC43Njc1eiIgZmlsbD0idXJsKCNsaW5lYXJHcmFkaWVudDI3NjQpIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOmV2ZW5vZGQ7ZmlsbDp1cmwoI2xpbmVhckdyYWRpZW50MjM3MSk7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggdHJhbnNmb3JtPSJtYXRyaXgoMS4wMTEzIDAgMCAxLjAwMDggNTM2LjIyIDQzMy43OSkiIGQ9Im0tNTA1LjQ4LTQxOS4yYzQuNzU3NCAwLjk3MDAyIDAuODY3MzQgMTAuNTIgMC4wMTU5IDEzLjE4NiAwLjAyNCAwLjAyNTEgMy4wNzg0LTUuNDAzNyA0LjAyMDMtOC45MTI5IDAuMDg5MS00LjUwNDYtMy44Njc0LTQuMzkyNi00LjAzNjItNC4yNzM0eiIgZmlsdGVyPSJ1cmwoI2ZpbHRlcjE4OTQtMSkiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBvcGFjaXR5PSIuMjUiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtmaWx0ZXI6dXJsKCNmaWx0ZXIxODk0LTEtNS01LTIpO21peC1ibGVuZC1tb2RlOm5vcm1hbDtvcGFjaXR5Oi4yO3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb24iLz4KIDxwYXRoIHRyYW5zZm9ybT0ibWF0cml4KDEuMDExMyAwIDAgMS4wMDA4IDUzNi4yMiA0MzMuNzkpIiBkPSJtLTUwNS40OS00MDIuMjYtOS4xZS00IC0wLjA3NTNjOS4xZS00IDAuMDc1MyAwLjAxNTYtMy4zOTUzIDAuMDE1Ni0zLjM5NTNzLTIuNjgwOS02Ljg5NTYtNC4wNTE2LTguOTE0MWMtMS4zMzM1LTEuOTYzNy0zLjc3ODItMi45Mjk1LTYuNTY0OC0yLjc4NDMgMy45NzIyIDEuNzY1MSA4LjkzNzIgMTAuMDI5IDEwLjYwMiAxNS4xNjl6IiBmaWx0ZXI9InVybCgjZmlsdGVyMTg5OC0wKSIgaW1hZ2UtcmVuZGVyaW5nPSJvcHRpbWl6ZVF1YWxpdHkiIG9wYWNpdHk9Ii4yNSIgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGwtcnVsZTpldmVub2RkO2ZpbHRlcjp1cmwoI2ZpbHRlcjE4OTgtMC00LTEtOSk7bWl4LWJsZW5kLW1vZGU6bm9ybWFsO29wYWNpdHk6LjI7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggZD0ibTYuMzE0MyAyOS41MjdjLTEuMTk2Mi0xLjY0NzMtMi4wMTEyLTMuODUwMS0xLjQ0NTctNi42MzU1IDEuNDM2NS03LjA3NDUgOS40NTctNi4yOTYzIDkuNDU3LTYuMjk2My0xMS41OTUgMy40Njc5LTcuODcxOSAxMi45NjItOC4wMTEzIDEyLjkzMnoiIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsLXJ1bGU6ZXZlbm9kZDtmaWxsOiNmZmZmZmY7ZmlsdGVyOnVybCgjZmlsdGVyMjM4NSk7aW1hZ2UtcmVuZGVyaW5nOm9wdGltaXplUXVhbGl0eTtvcGFjaXR5Oi4zO3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb24iLz4KIDxwYXRoIGQ9Im0yMy41NyAyMy4wMjUtMi4xMTM1LTQuNDAyNHMtMC42OTA1NS00LjExOTkgMy41NDAyLTQuMDU2Yy00LjIxMyAxLjMzMS0xLjQyNjcgOC40NTg1LTEuNDI2NyA4LjQ1ODV6IiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOmV2ZW5vZGQ7ZmlsbDojZmZmZmZmO2ZpbHRlcjp1cmwoI2ZpbHRlcjIzOTMpO2ltYWdlLXJlbmRlcmluZzpvcHRpbWl6ZVF1YWxpdHk7b3BhY2l0eTouMjU7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggZD0ibTI3LjAyNiAyNC45NTkgMi43NTIxLTUuOTI4N3MyLjQ0NjYtMi4yMjM2IDYuMTE2NC0yLjU3MTVjLTMuOTQ3NSAxLjAzMjEtNi4zMzAxIDQuODc0Ny04Ljg2ODUgOC41MDAzeiIgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGwtcnVsZTpldmVub2RkO2ZpbGw6I2ZmZmZmZjtmaWx0ZXI6dXJsKCNmaWx0ZXIyMzg5KTtpbWFnZS1yZW5kZXJpbmc6b3B0aW1pemVRdWFsaXR5O29wYWNpdHk6LjI7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggZD0ibTI1LjMxOCA0MS45MjRjLTEwLjMxMSAxZS00IC0xNC41NjQgMi4xODI3LTE0LjU2NCAyLjE4MjdzNC4yNTI5IDIuMTgyNSAxNC41NjQgMi4xODI1aDAuMDIxMjRjLTE1LjQ0MS0xLjk5IDcuMzE0OC0zLjgwMyA4LjI4NDItMy44MTA3LTIuMTczMy0wLjMxNjQyLTQuOTE3MS0wLjU1MzgzLTguMzA1Mi0wLjU1MzczeiIgaW1hZ2UtcmVuZGVyaW5nPSJvcHRpbWl6ZVF1YWxpdHkiIG9wYWNpdHk9Ii4yIiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOmV2ZW5vZGQ7b3BhY2l0eTouMTU7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggZD0ibTI1IDQ2LjI5NmMtOS4yNDEgOGUtNSAtMTMuNzQ0LTIuMjA4OC0xMy43NDQtMi4yMDg4czQuNTAzLTIuMjA4NyAxMy43NDQtMi4yMDg4YzkuMjQxLThlLTUgMTMuNzQ0IDIuMjA4OCAxMy43NDQgMi4yMDg4cy00LjUwMyAyLjIwODctMTMuNzQ0IDIuMjA4OHoiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBvcGFjaXR5PSIuMiIgc3Ryb2tlPSIjMDAwMDAwIiBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOmV2ZW5vZGQ7b3BhY2l0eTouMjtzaGFwZS1yZW5kZXJpbmc6Z2VvbWV0cmljUHJlY2lzaW9uO3N0cm9rZS13aWR0aDowIi8+CiA8cGF0aCBkPSJtMTIuNTI4IDM5LjE2OWMwLjg1MDE0LTAuNDQ5NzIgMS43Mjg4LTAuNjQyODYgMi41OTM0LTAuOTYxOTEtMC41ODc4NiAwLjg0MDk2LTAuNjM0NCAyLjcyMzktMC4zNTcyMyA0LjA2MjMgMCAwLTAuODkyMDEgMC4xMjQyMy0zLjIzMTggMC45MDQyOXoiIGZpbGw9IiNmZmZmZmYiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBvcGFjaXR5PSIuNyIgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGwtcnVsZTpldmVub2RkO2ZpbHRlcjp1cmwoI2ZpbHRlcjIzNzcpO29wYWNpdHk6LjE7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbiIvPgogPHBhdGggZD0ibTEyLjQ2NCAzOC4wMjFzMC4zMDA2MS0wLjI4Nzc2IDIuNDE2Mi0wLjg3NjgzYy0xLjU5MS0xLjYwMTItMS40MDAyLTMuNDQ2Mi0xLjU3NDctMy41MTYtMC41NjY1IDAuMTY3OS0xLjExMTggMC4zODg5My0xLjY1IDAuNjI3NHoiIGZpbGw9IiNmZmZmZmYiIGltYWdlLXJlbmRlcmluZz0ib3B0aW1pemVRdWFsaXR5IiBvcGFjaXR5PSIuOSIgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGwtcnVsZTpldmVub2RkO2ZpbHRlcjp1cmwoI2ZpbHRlcjIzNzMpO29wYWNpdHk6LjE1O3NoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb24iIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY2MiLz4KPC9zdmc+Cg==')
}
\ No newline at end of file

M web/src/components/App.js => web/src/components/App.js +7 -1
@@ 9,7 9,13 @@ export default function App(props) {
    const [user, setUser] = useState(null);

    useEffect(() => {
        api.get("/api/me").then(setUser);
        api.get("/api/me").then(user => {
            if (user && Object.keys(user).length > 0) {
                setUser(user);
            } else {
                setUser(null);
            }
        });
    }, []);

    return (

M web/src/components/Puzzle.js => web/src/components/Puzzle.js +13 -7
@@ 1,5 1,5 @@
import { Chessboard } from 'react-chessboard';
import { useState, useEffect } from 'react'
import { useState, useEffect, Fragment } from 'react'
import { useParams, useNavigate } from "react-router-dom";
import Chess from 'chess.js';
import api from "../api.js";


@@ 86,7 86,7 @@ function Puzzle() {
            setBoardConfig({ ...boardConfig, arrows: [[bestMoves[0].substring(0, 2), bestMoves[0].substring(2, 4)]] })
        }
        setResult(result ? GOOD : BAD);
        api.post(`/api/puzzles/${puzzle.id}?bucket=${result ? 2 : 1}`).then(() => {
        api.post(`/api/puzzles/${puzzle.id}?correct=${result ? 1 : 0}`).then(() => {
            loadNextPuzzle(timeout)
        })
    }


@@ 94,17 94,23 @@ function Puzzle() {
    const renderResult = (result) => {
        switch (result) {
            case PLAYING:
                return (`Find the best move for ${boardConfig.orientation === 'white' ? 'white' : 'black'}.`);
                return (
                    <Fragment>
                    <div className={"king " + boardConfig.orientation}></div>

                    <div><strong>Your turn</strong><br />Find the best move for {boardConfig.orientation === 'white' ? 'white' : 'black'}.</div>
                    </Fragment>
                )

            case GOOD:
                return ("✅ Correct!");
                return (<p>✅ Correct!</p>);

            case BAD:
                return ("❌ Sorry, that's not the best move.");
                return (<p>❌ Sorry, that's not the best move.</p>);
        }
    }

    const width = Math.min(window.innerWidth - 40, 720);
    const width = Math.min(window.innerWidth - 40, 580);

    return (
        <div key={`puzzle-${id}`}>


@@ 114,7 120,7 @@ function Puzzle() {
            </header>
            <div className="board-wrap">
                <Chessboard id="board" boardWidth={width} position={game.fen()} onPieceDrop={onDrop} boardOrientation={boardConfig.orientation} customArrows={boardConfig.arrows} customArrowColor='green' />
                <div className="result"><p>{renderResult(result)}</p></div>
                <div className="result">{renderResult(result)}</div>
            </div>
        </div>
    )