Apply velvet-table redesign, fix game lifecycle and history bugs
Frontend: - Dark green/gold "velvet table" visual redesign across the whole app (Auth, Lobby, GameList, GameTable, History, GameOver, modals), with Playfair Display/DM Sans typography and a centralized Tailwind palette. - Desktop game table fit-scales to fill the window; mobile gets overlapping hand/trick layouts and larger touch-friendly cards. - Standings sidebar now groups completed rounds by series with a per-series subtotal row, struck-through tips on missed bids. - History page rewritten into a scoreboard-style detail view (player totals beside names, series grouped 2-up on desktop / stacked on mobile) and gained game names, completed/abandoned status, and a button to reopen a prematurely-ended game back into the lobby. Backend: - Fix started games being deleted from memory (and vanishing from everyone's lobby) when all players disconnect; only `end_game` tears down a started game now. - Fix a crash writing a timezone-aware datetime into the naive `ended_at` Postgres column. - Add `reopen_game`/`restore_game` to un-end a prematurely-ended game from history and resume it from the lobby. - Let any seated player end an abandoned game once the host is offline, not just the host, so the game isn't stuck forever. - Expose SERIES_PER_GAME/ROUNDS_PER_SERIES as named constants on the engine so the persistence layer derives game-completion rules from bridzik.py instead of re-encoding them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+28
-2
@@ -143,15 +143,41 @@ class HistoryCase(unittest.TestCase):
|
||||
run(history.mark_game_ended(gid))
|
||||
self.assertFalse(any(g["gid"] == gid for g in run(history.get_unfinished_games())))
|
||||
|
||||
def test_reopen_prematurely_ended_game(self):
|
||||
ids = self._make_players()
|
||||
gid = str(uuid.uuid4())
|
||||
run(history.record_game_started(gid, "Vzdana", ids))
|
||||
run(history.record_completed_rounds(gid, make_core(completed=False)))
|
||||
run(history.mark_game_ended(gid))
|
||||
|
||||
# V historii sa ukazuje ako predcasne ukoncena (nie naplno dohrana).
|
||||
rows = run(history.get_player_history(ids[0]))
|
||||
mine = next(g for g in rows if g["gid"] == gid)
|
||||
self.assertFalse(mine["completed"])
|
||||
|
||||
# Cudzi hrac ju obnovit nemoze.
|
||||
outsider = self._make_players(1)[0]
|
||||
self.assertIsNone(run(history.reopen_game(gid, outsider)))
|
||||
|
||||
# Clen ju obnovi -> ended_at sa zmaze a hra je zas medzi nedohratymi.
|
||||
info = run(history.reopen_game(gid, ids[0]))
|
||||
self.assertIsNotNone(info)
|
||||
self.assertEqual(len(info["seats"]), 4)
|
||||
self.assertTrue(any(g["gid"] == gid for g in run(history.get_unfinished_games())))
|
||||
# A teda uz nie je v historii (zobrazuju sa iba ukoncene hry).
|
||||
self.assertFalse(any(g["gid"] == gid for g in run(history.get_player_history(ids[0]))))
|
||||
|
||||
def test_standings_from_db(self):
|
||||
ids = self._make_players()
|
||||
gid = str(uuid.uuid4())
|
||||
run(history.record_game_started(gid, "Test", ids))
|
||||
run(history.record_completed_rounds(gid, make_core()))
|
||||
|
||||
standings = run(history.get_standings(gid))
|
||||
# 1 seria, 1 kolo, body podla sedadiel zo stubu [12, 0, 10, 11].
|
||||
standings, guesses = run(history.get_standings(gid))
|
||||
# 1 seria, 1 kolo. Body podla sedadiel zo stubu [12, 0, 10, 11],
|
||||
# tipy zo stubu {0: 2, 1: 1, 2: 0, 3: 1}.
|
||||
self.assertEqual(standings, [[[12, 0, 10, 11]]])
|
||||
self.assertEqual(guesses, [[[2, 1, 0, 1]]])
|
||||
|
||||
def test_player_history(self):
|
||||
ids = self._make_players()
|
||||
|
||||
Reference in New Issue
Block a user