feat: add scoreboard with player names, per-category leaderboards, and themed tables

Add player_name column to games (migration included), with regex-based
validation and anti-abuse sanitization. New /scoreboard page shows recent
games from localStorage, popular categories with play counts, and top-10
per-category leaderboards sorted by completion time. Also includes
two-click reverse word matching, base URL prefix support for reverse-proxy
hosting, BasePageHandler refactoring, themed table CSS for all 5 themes,
and comprehensive test coverage for player names and scoreboard API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mathew Sir Guest the best
2026-05-07 10:38:19 -06:00
co-authored by Claude Opus 4.6
parent 9319f08875
commit 36c3c712fe
27 changed files with 3038 additions and 24 deletions
+9 -1
View File
@@ -290,11 +290,19 @@ class GuessHandler(BaseAPIHandler):
except (IndexError, KeyError, TypeError):
return self._miss_response(game, session)
# Try forward and reversed letter order
reversed_letters = letters[::-1]
word = (
session.query(Word)
.filter_by(game_id=game.id, text=letters, found=False)
.first()
)
if not word:
word = (
session.query(Word)
.filter_by(game_id=game.id, text=reversed_letters, found=False)
.first()
)
if not word:
return self._miss_response(game, session)
@@ -303,7 +311,7 @@ class GuessHandler(BaseAPIHandler):
{"row": word.start_row + i * dr, "col": word.start_col + i * dc}
for i in range(len(word.text))
]
if cells != expected_cells:
if cells != expected_cells and list(reversed(cells)) != expected_cells:
return self._miss_response(game, session)
return self._mark_found(session, game, word)