Averaging only the winners made hard days look easy
Above the board in my Wordle Mini App there is a quiet grey line: how many people have played today’s puzzle across the whole edition, and how many guesses it took them on average. It is ambient — nobody opens the app for it — but it is the only thing on the screen that says anything about the day rather than about you.
It shipped counting solvers, and averaging over solvers. Both halves were wrong in the same direction, and the direction is the one that matters: on a hard word, the players the word beat leave the statistic entirely. The count falls, because fewer people solved it. And the average falls too, or at best holds still, because the only people in it are the ones who managed. The number meant to tell you “today is rough” gets quieter and prettier the rougher today actually is.
The arithmetic is not subtle. Take a day where the winners average 4.0 guesses and a fifth of
the people who finished did not win. Counting the losses at their true worth gives
0.8 × 4.0 + 0.2 × 7 = 4.6. Six tenths of a guess is the whole visible range of this stat —
easy days and hard days live inside about a point of each other — and it was being thrown away
by the players who most deserved to be in the number.
A loss is 7
Seven, because a loss is not missing data. It is the worst outcome the game has, and Wordle
already has a notation for it: the share grid writes X/6. So a loss scores MAX_ATTEMPTS + 1,
the average lives in 1..7, and it moves with difficulty instead of against it.
select count(*)::int as played,
avg(case when status = 'won' then attempts else 7 end)::float8 as avg_attempts
from games
where puzzle_date = $1
and status in ('won', 'lost')
and not excluded
The count changed with it. “Solved today” and “played today” are different questions, and the one worth asking a player who is halfway through their own board is how many people are out there doing the same thing — not how many have already beaten them.
What it costs
Games still in progress stay out of both numbers, and that is a real mismatch with the word “played”. Someone on their third guess is not a 3; they have no final score at all, and putting them in would drag the average toward whatever people happen to have typed so far, which is a number about the clock rather than about the word. So “played” means “finished today”, and a player is not in the line they are looking at until their own board is done. I took that over an average that quietly re-computes itself as the evening fills up.
The other casualty was a comment. The stat rides the gameplay path — every guess re-reads it — so it is cached per puzzle date for twenty seconds and the cache is dropped whenever a game finishes. The invalidation was already unconditional on finish, and the comment above it explained why: a loss does not move the numbers, but invalidating on any finish keeps the code simple. That reasoning was true when it was written and false the moment losses started counting. The code needed nothing; the comment needed rewriting, because it was exactly the kind of note a future reader trusts when deciding it is safe to skip the cheap path on a loss.
What is still slightly off is the crowd size. “Played” now means everyone who reached an ending, so a chat full of people who opened the board, guessed twice and wandered off still shows up as a smaller day than it was. That undercount is honest in a way the old one wasn’t — it excludes people who have no result yet, rather than people who got the worst one.