More achievements
This commit is contained in:
@@ -265,11 +265,16 @@ def write_state(connection, state):
|
||||
|
||||
|
||||
def milestone_status(game_state, at):
|
||||
"""Eight fixed community goals; elapsed survival stops at the deadline."""
|
||||
"""Twelve 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"])
|
||||
scores = {}
|
||||
visited = set()
|
||||
for press in game_state["presses"]:
|
||||
scores[press["player_id"]] = scores.get(press["player_id"], 0) + press["points"]
|
||||
visited.add(press["zone"])
|
||||
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),
|
||||
@@ -279,6 +284,10 @@ def milestone_status(game_state, at):
|
||||
("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),
|
||||
("shared_spectrum", "Every shade together", "The community has pressed in every scoring zone this season!", len(visited & {z["name"] for z in ZONES}), len(ZONES)),
|
||||
("ten_contenders", "A race worth watching", "Ten players have earned points on this season's leaderboard!", sum(points > 0 for points in scores.values()), 10),
|
||||
("thousand_points", "A thousand points together", "The seasonal leaderboard has reached 1,000 combined points!", sum(scores.values()), 1000),
|
||||
("ten_thousand_points", "A season to remember", "The community has earned 10,000 points this season!", sum(scores.values()), 10000),
|
||||
]
|
||||
return [{"id": key, "name": name, "message": message, "progress": min(progress, target), "target": target}
|
||||
for key, name, message, progress, target in goals]
|
||||
@@ -287,14 +296,14 @@ def milestone_status(game_state, at):
|
||||
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):
|
||||
existed = connection.execute("SELECT 1 FROM community_milestones WHERE id = ?", (goal["id"],)).fetchone() is not None
|
||||
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"]),
|
||||
(at, at if existed else None, f"Milestone: {goal['name']}. {goal['message']}", goal["id"]),
|
||||
)
|
||||
|
||||
|
||||
@@ -614,6 +623,41 @@ def empty_zone_counts():
|
||||
return {z["name"]: 0 for z in ZONES}
|
||||
|
||||
|
||||
def seasonal_achievements(pid):
|
||||
"""Lifetime badges from immutable archived presses, keyed by player ID, not names."""
|
||||
with database() as connection:
|
||||
exists = connection.execute("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'season_results'").fetchone()
|
||||
rows = connection.execute("SELECT finished_at, presses FROM season_results ORDER BY finished_at, starts_at").fetchall() if exists else []
|
||||
finished = 0
|
||||
finisher_at = veteran_at = podium_at = champion_at = None
|
||||
for row in rows:
|
||||
scores = {}
|
||||
for press in json.loads(row["presses"]):
|
||||
scores[press["player_id"]] = scores.get(press["player_id"], 0) + press["points"]
|
||||
if pid not in scores:
|
||||
continue
|
||||
finished += 1
|
||||
if finisher_at is None:
|
||||
finisher_at = row["finished_at"]
|
||||
if finished >= 2 and veteran_at is None:
|
||||
veteran_at = row["finished_at"]
|
||||
# Competition ranks: tied scores share rank; never use alphabetical order for awards.
|
||||
rank = 1 + sum(points > scores[pid] for points in scores.values())
|
||||
if rank <= 3 and podium_at is None:
|
||||
podium_at = row["finished_at"]
|
||||
if rank == 1 and champion_at is None:
|
||||
champion_at = row["finished_at"]
|
||||
rules = [
|
||||
("season_finisher", "Part of the story", "Participate with a press in a completed season.", finisher_at, min(finished, 1), 1),
|
||||
("season_veteran", "Back for another", "Participate in two completed seasons.", veteran_at, min(finished, 2), 2),
|
||||
("season_podium", "Podium finish", "Finish a season in the top three by points. Ties share rank.", podium_at, int(podium_at is not None), 1),
|
||||
("season_champion", "Season champion", "Finish a season with the highest score. Joint winners qualify.", champion_at, int(champion_at is not None), 1),
|
||||
]
|
||||
return [{"id": key, "name": name, "description": description, "unlocked": at is not None,
|
||||
"unlocked_at": at, "progress": progress, "target": target, "scope": "lifetime"}
|
||||
for key, name, description, at, progress, target in rules]
|
||||
|
||||
|
||||
def player_achievements(pid):
|
||||
"""Derive private achievements from recorded history, including legacy games."""
|
||||
quiet_target = max(1, state["duration_ms"] // 2)
|
||||
@@ -667,7 +711,7 @@ def player_achievements(pid):
|
||||
"unlocked_at": extra_unlocks[achievement_id],
|
||||
"progress": int(extra_unlocks[achievement_id] is not None), "target": 1}
|
||||
for achievement_id, name, description, _, _ in extra_rules
|
||||
]
|
||||
] + seasonal_achievements(pid)
|
||||
|
||||
|
||||
def player_summary(pid):
|
||||
|
||||
Reference in New Issue
Block a user