casebench
test cases straight onto the board.
git clone https://owenrusk.dev/casebench.git
commit 991e1e7c89d98f4e4ac4028b4ac6770be6ce857b parent b44d61677233222bf05fc0f12d066d262ca5089b author Owen Rusk <owen@papermothgames.com> date 2026-02-03 15:58:21 -0600
list: show locks and note counts and runtime cases the bench has locked, which it didn't show before.
| casebench/cli.py | +8 | -2 |
| casebench/lock.py | +8 | -0 |
| casebench/memstore.py | +5 | -2 |
| casebench/pgstore.py | +14 | -5 |
| casebench/store.py | +2 | -0 |
diff --git a/casebench/cli.py b/casebench/cli.py index 6b897cb..30f3201 100644 --- a/casebench/cli.py +++ b/casebench/cli.py @@ -159,8 +159,14 @@ def run_unlock(store: Store, args: argparse.Namespace) -> None: def run_list(store: Store, args: argparse.Namespace) -> None: for who in targets(store, args): for placed in store.board(who):- if placed.source == BENCH:- print(f"{who.handle}\t{placed.case_id}\t{placed.title}")+ if placed.source != BENCH and placed.lock is None: + continue # the runtime's own, untouched + marks = [placed.source] + if placed.lock: + marks.append(placed.lock) + if placed.notes: + marks.append(f"{placed.notes} note" + ("" if placed.notes == 1 else "s")) + print("\t".join([who.handle, placed.case_id, placed.title, ", ".join(marks)])) def run_pull(store: Store, args: argparse.Namespace) -> None: diff --git a/casebench/lock.py b/casebench/lock.py index 475800c..5c78e1b 100644 --- a/casebench/lock.py +++ b/casebench/lock.py @@ -61,6 +61,14 @@ def make(answers: list[str], rest: Rest, text: str = "locked.") -> Lock: return Lock(text, len(answers), salt, tuple(hashes), rest) +def state(lock: Lock, now: datetime) -> str: + if lock.opened_at is not None: + return "open" + if lock.resting_until is not None and now < lock.resting_until: + return "resting" + return "locked" + + def attempt(lock: Lock, typed: list[str], now: datetime) -> tuple[str, Lock]: # the board calls this when someone types into a lock. the lines are # checked as one, so a wrong try never says which line was right. diff --git a/casebench/memstore.py b/casebench/memstore.py index a0e197b..7f5e099 100644 --- a/casebench/memstore.py +++ b/casebench/memstore.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, field from datetime import UTC, datetime from .casefile import Case, Image-from .lock import Lock+from .lock import Lock, state from .store import BENCH, Placed, Player @@ -50,7 +50,10 @@ class MemStore: def placed(self, player: Player, case_id: str) -> Placed | None: case = self.cases.get((player.id, case_id))- return None if case is None else Placed(case_id, case.title, case.source, case.placed_at)+ if case is None: + return None + held = None if case.lock is None else state(case.lock, datetime.now(UTC)) + return Placed(case_id, case.title, case.source, case.placed_at, held, len(case.notes)) def board(self, player: Player) -> list[Placed]: found = [self.placed(player, case_id) for pid, case_id in self.cases if pid == player.id] diff --git a/casebench/pgstore.py b/casebench/pgstore.py index c8a06a2..b0429eb 100644 --- a/casebench/pgstore.py +++ b/casebench/pgstore.py @@ -6,6 +6,15 @@ from .casefile import Case, Image from .lock import Lock, Rest from .store import Placed, Player +PLACED = ( + "select c.case_id, c.title, c.source, c.placed_at," + " case when l.opened_at is not null then 'open'" + " when l.resting_until > now() then 'resting'" + " when l.case_id is not null then 'locked' end," + " (select count(*) from board_notes n where n.player_id = c.player_id and n.case_id = c.case_id)" + " from board_cases c left join board_locks l on l.player_id = c.player_id and l.case_id = c.case_id" +) + class PgStore: # the hub's postgres, underneath the runtime. @@ -26,15 +35,13 @@ class PgStore: def placed(self, player: Player, case_id: str) -> Placed | None: row = self.conn.execute(- "select case_id, title, source, placed_at from board_cases where player_id = %s and case_id = %s",- (player.id, case_id),+ PLACED + " where c.player_id = %s and c.case_id = %s", (player.id, case_id) ).fetchone() return Placed(*row) if row else None def board(self, player: Player) -> list[Placed]: rows = self.conn.execute(- "select case_id, title, source, placed_at from board_cases where player_id = %s order by placed_at",- (player.id,),+ PLACED + " where c.player_id = %s order by c.placed_at", (player.id,) ).fetchall() return [Placed(*row) for row in rows] @@ -108,7 +115,9 @@ class PgStore: if row is None: return None text, lines, salt, answer_hashes, after, minutes, wrong, resting_until, opened_at = row- return Lock(text, lines, bytes(salt), tuple(answer_hashes), Rest(after, minutes), wrong, resting_until, opened_at)+ return Lock( + text, lines, bytes(salt), tuple(answer_hashes), Rest(after, minutes), wrong, resting_until, opened_at + ) def unlock(self, player: Player, case_id: str) -> bool: gone = self.conn.execute( diff --git a/casebench/store.py b/casebench/store.py index 39f45ba..f2d549e 100644 --- a/casebench/store.py +++ b/casebench/store.py @@ -20,6 +20,8 @@ class Placed: title: str source: str placed_at: datetime + lock: str | None = None # locked, resting or open + notes: int = 0 class Store(Protocol):