Community milestones

This commit is contained in:
2026-10-21 13:26:16 -04:00
parent a9f578571c
commit 1ff14c8d15
5 changed files with 203 additions and 13 deletions
+53 -1
View File
@@ -156,6 +156,40 @@ def write_state(connection, state):
)
def milestone_status(game_state, at):
"""Eight fixed community goals; elapsed survival stops at the deadline."""
until = min(at, game_state["ended_at"] if game_state["ended_at"] is not None else game_state["deadline"])
elapsed = max(0, until - game_state["started_at"])
players = len(game_state["players"])
presses = len(game_state["presses"])
goals = [
("day_one", "One day together", "The community kept the game alive for one day!", elapsed, 86400000),
("one_week", "A week of nerve", "Seven days together. The clock is still part of our story!", elapsed, 7 * 86400000),
("one_month", "Thirty days strong", "The game survived 30 days. What a community!", elapsed, 30 * 86400000),
("ten_players", "A growing crowd", "Ten players have joined the experiment!", players, 10),
("fifty_players", "Fifty strong", "Fifty players, one shared clock. Welcome aboard!", players, 50),
("hundred_presses", "A hundred second chances", "Together we've pressed the button 100 times!", presses, 100),
("thousand_presses", "A thousand moments", "1,000 presses. A thousand decisions to keep us going!", presses, 1000),
("first_photo_finish", "Our first photo finish", "A community first: someone pressed with less than ten seconds left!", int(any(0 < p["remaining_ms"] < 10000 for p in game_state["presses"])), 1),
]
return [{"id": key, "name": name, "message": message, "progress": min(progress, target), "target": target}
for key, name, message, progress, target in goals]
def sync_milestones(connection, game_state, at=None):
"""Commit unlocks with game changes. Existing goals are silently baselined once."""
at = now_ms() if at is None else at
initialized = connection.execute("SELECT 1 FROM community_milestones LIMIT 1").fetchone() is not None
for goal in milestone_status(game_state, at):
connection.execute("INSERT OR IGNORE INTO community_milestones (id) VALUES (?)", (goal["id"],))
if goal["progress"] >= goal["target"]:
connection.execute(
"""UPDATE community_milestones SET reached_at = ?, announced_at = ?, message = ?
WHERE id = ? AND reached_at IS NULL""",
(at, at if initialized else None, f"Milestone: {goal['name']}. {goal['message']}", goal["id"]),
)
def load_state():
if DATABASE_FILE.resolve() == DATA_FILE.resolve():
raise ValueError("BUTTON_DATABASE_FILE must differ from the legacy BUTTON_DATA_FILE")
@@ -199,6 +233,12 @@ def load_state():
message TEXT NOT NULL CHECK (length(message) BETWEEN 1 AND 200)
);
CREATE INDEX IF NOT EXISTS chat_player_messages ON chat_messages (player_id, id);
CREATE TABLE IF NOT EXISTS community_milestones (
id TEXT PRIMARY KEY,
reached_at INTEGER,
announced_at INTEGER,
message TEXT
);
""")
columns = {row["name"] for row in connection.execute("PRAGMA table_info(chat_messages)")}
if "kind" not in columns:
@@ -219,6 +259,7 @@ def load_state():
# Any read/validation/SQL failure aborts startup without consuming the
# JSON source or committing a partial game. The next start can retry.
write_state(connection, loaded)
sync_milestones(connection, loaded)
return loaded
loaded = dict(game)
@@ -238,12 +279,14 @@ def load_state():
press = dict(row)
del press["position"]
loaded["presses"].append(press)
sync_milestones(connection, loaded)
return loaded
def save_state(state):
with database() as connection:
write_state(connection, state)
sync_milestones(connection, state)
state = load_state()
@@ -274,7 +317,12 @@ def public_presence():
def chat_messages():
with database() as connection:
rows = connection.execute(
"SELECT id, name, at, message, kind FROM chat_messages ORDER BY id DESC LIMIT ?",
"""SELECT id, name, at, message, kind FROM (
SELECT id, name, at, message, kind, 0 AS source, id AS position FROM chat_messages
UNION ALL
SELECT 'milestone:' || id, 'The Button', announced_at, message, 'milestone', 1, rowid
FROM community_milestones WHERE announced_at IS NOT NULL
) ORDER BY at DESC, source DESC, position DESC LIMIT ?""",
(CHAT_HISTORY_LIMIT,),
).fetchall()
return [dict(row) for row in reversed(rows)]
@@ -319,6 +367,9 @@ def check_ended():
state["ended"] = True
state["ended_at"] = state["deadline"]
save_state(state)
else:
with database() as connection:
sync_milestones(connection, state)
def empty_zone_counts():
@@ -737,6 +788,7 @@ class Handler(BaseHTTPRequestHandler):
"INSERT INTO chat_messages (player_id, name, at, message, kind) VALUES (?, ?, ?, ?, 'press')",
(pid, "The Button", t, announcement),
)
sync_milestones(connection, state, t)
except Exception:
# Keep memory consistent with the rolled-back database snapshot.
state["presses"].pop()