Spaces:
Running
Download README.md from FineEnvs/ScottWuMath: direct link, hf CLI and curl.
- Browser
- Download file 8.59 kB
-
https://huggingface.co/spaces/FineEnvs/ScottWuMath/resolve/main/README.md
- Command line
-
hf download hf://spaces/FineEnvs/ScottWuMath/README.md
-
curl -L -o README.md https://huggingface.co/spaces/FineEnvs/ScottWuMath/resolve/main/README.md
title: Scott Wu Math
emoji: π
colorFrom: green
colorTo: gray
sdk: docker
app_port: 7860
pinned: false
license: apache-2.0
short_description: Scott Wu's card game as an OpenEnv RL environment
tags:
- openenv
- reinforcement-learning
- math
- reasoning
Scott Wu Math
Deal some playing cards, name a number, and make that number from the cards with + β Γ Γ·, using every
card exactly once. People and language models play the same environment: the page at / drives the
same OpenEnv session over /ws that an agent does.
Named after the game Scott Wu plays in the clip below, as a fan tribute. It is not affiliated with or endorsed by Scott Wu or Cognition.
Where it comes from
In a clip filmed at a pub, Cognition's CEO Scott Wu explains the game he plays with a deck of cards. It starts as the 24 game: four cards, make 24. "Once that got too easy", you take eight cards and make the current year. Dealt K Q J 10 7 9 5 5, he says it is quite easy because 13 β 12 is 1, 11 β 10 is 1, 1 + 1 + 7 is 9, and 9 Γ 9 Γ 5 Γ 5 is exactly 2025. Then someone names a four-digit number, 6,843, and with nine cards (6 4 Q 10 10 8 J 7 3) he goes: 6 + 4 is 10, 12 β 10 is 2, 2 Γ 10 Γ 10 is 200, plus 8 is 208, times 11 is 2,288, minus 7 is 2,281, times 3 is 6,843.
Both hands are in the video split, as he played them.
Rules
- Use every card exactly once. A = 1, J = 11, Q = 12, K = 13.
- Combine with + β Γ Γ· and parentheses. Division is exact, so fractions along the way are allowed, as in the 24 game.
- No joining cards into longer numbers, no powers, no unary minus.
- One graded answer per deal.
Levels
| Level | Cards | Target | Notes |
|---|---|---|---|
24 |
4 | 24 | The classic 24 game. About a third of random deals have no solution; those are redealt. |
three-digit |
6 | 100β999 | A step between 24 and the year. |
year |
8 | 1900β2100 | The play page and a bare reset() use the current year, as in the video. |
four-digit |
9 | 1000β9999 | "Give me your favourite four-digit number". |
| custom | 4β9 | any up to 1,000,000 | reset(target=6843); card count by size unless n_cards is given. |
Cards come from one 52-card deck, so a value appears at most four times. Every generated deal is checked with the solver and redealt until it has a solution, so a task is never impossible.
Episode
reset(...) -> observation: cards, target, prompt (done = false)
step(CardAction(expression="...")) -> reward, result, a reference solution (done = true)
reset arguments, all optional:
| Argument | Meaning |
|---|---|
split, index |
A fixed task from train (4,000), test (200) or video (2). Without index, a random one. |
level |
A fresh deal at that level. |
target, n_cards |
A fresh deal for your own number. |
cards + target |
Your own hand, as values 1β13. It may have no solution. |
seed |
Makes a fresh deal reproducible. |
reward_mode |
shaped (default) or binary. |
The observation carries prompt and system_prompt, the exact text a model reads. The answer goes in
CardAction.expression; an empty expression gives up. After the step, the observation adds result
(the grader's report), solution and solution_steps (one way to make the target, one combination
per line), and elapsed_s. The reference solution is never shown before the episode ends.
Reward
| Answer | Reward |
|---|---|
| Uses every card once and equals the target | 1.0 |
| Uses every card once, within 10% of the target (shaped mode) | 0.3 Γ cΒ², `c = 1 β |
| Anything else: no answer, parse error, division by zero, a missing or extra card | 0 |
The grader parses the expression with Python's ast, allows only non-negative integers, + β Γ Γ· and
parentheses, compares the multiset of numbers to the cards, and evaluates with exact fractions. Γ Γ· β
and the letters A J Q K are accepted; anything after = is ignored. Partial credit keeps a GRPO group of
near misses from all scoring 0, and is capped far below an exact answer, so getting close never pays
more than getting it right.
Use it
from scott_wu_math import CardAction, ScottWuMathEnv
with ScottWuMathEnv(base_url="https://fineenvs-scottwumath.hf.space").sync() as env:
obs = env.reset(split="video", index=1).observation
print(obs.prompt)
result = env.step(CardAction(expression="(((12 - 10) * (6 + 4) * 10 + 8) * 11 - 7) * 3"))
print(result.reward, result.observation.result["check"]["value"]) # 1.0 6843
Over plain WebSocket (what the play page does): connect to /ws, send
{"type": "reset", "data": {"level": "four-digit"}}, then {"type": "step", "data": {"expression": "..."}}.
Task discovery follows OpenEnv's Task API: GET /scott_wu_math/splits, POST /scott_wu_math/tasks
with {"split": "test"}, and so on. Task listings leave out the reference solutions.
For text-only RL (no server), scott_wu_math.game.reward.grade(expression, card_values, target) and
extract_answer(completion) are the same grader as a plain function, and game.prompts.messages(cards, target)
builds the same prompt.
The solver
Every expression tree splits the cards at its root into two groups, and the smaller group has at most
half the cards. The solver enumerates every value a group of up to four cards can make, then asks the
remaining cards for the one number that completes the target (target = v + rest needs
rest = target β v, and so on for the six ways to place v), recursing until four cards remain, where
the answer is a table lookup. That is complete for up to nine cards, and finds a solution for an
eight- or nine-card deal in a few milliseconds. A first pass keeps every intermediate value a whole
non-negative number, the way people play; a second pass allows fractions.
Run locally
pip install -e .
uvicorn scott_wu_math.server.app:app --port 8012 # play at http://localhost:8012
python scripts/build_tasks.py # rebuild data/tasks (deterministic)
pytest
Evaluations
eval/rollout.py in the FineEnvs repository
runs OpenAI, Anthropic and open models on Hugging Face Inference Providers against a running server, in
parallel, one OpenEnv session per episode.
Results (2026-10-23)
Twelve models on the first five test deals of each level (20 deals, one sample each) and on the two
video hands (three samples each), in three modes:
- Reasoning: the model's own reasoning at high effort, up to 32k output tokens.
- No reasoning: reasoning switched off, or as low as the API allows (
*), up to 32k tokens, so the model may still work it out in its visible reply. - At once: no reasoning, a system line asking for the answer tag alone, and 300 output tokens: the mental-arithmetic version of the game.
| Model | Reasoning | No reasoning | At once | Video hands: reasoning / none / at once |
|---|---|---|---|---|
| GPT-6.1 Sol | 20/20 | 20/20* | 14/20* | 6/6 / 6/6 / 3/6 |
| Claude Opus 5.5 | 20/20 | 19/20* | 8/20* | 6/6 / 6/6 / 5/6 |
| GLM-5.3 | 17/20 | 19/20 | 5/20 | 6/6 / 5/6 / 0/6 |
| Qwen3.8 2.4T | 20/20 | 19/20 | 4/20 | 6/6 / 5/6 / 0/6 |
| Claude Sonnet 5.5 | 20/20 | 15/20 | 4/20 | 6/6 / 4/6 / 0/6 |
| Claude Haiku 5.5 | 20/20 | 11/20 | 3/20 | 6/6 / 5/6 / 0/6 |
| Kimi K2.6 | 19/20 | 20/20 | 2/20 | 6/6 / 6/6 / 0/6 |
| DeepSeek V4 Pro | 20/20 | 10/20 | 2/20 | 6/6 / 3/6 / 0/6 |
| gpt-oss-120b | 16/20 | 6/20* | 2/20* | 6/6 / 1/6 / 0/6 |
| GPT-6 Luna | 20/20 | 2/20 | 2/20 | 5/6 / 0/6 / 0/6 |
| Qwen3.8 27B | 18/20 | 20/20 | 1/20 | 6/6 / 6/6 / 0/6 |
| Gemma 4 31B | 19/20 | 18/20 | 1/20 | 5/6 / 5/6 / 0/6 |
* lowest effort the API allows: GPT-6.1 Sol and gpt-oss-120b at low, Claude Opus 5.5 at low (its
thinking cannot be disabled). Open models ran on Hugging Face Inference Providers (Qwen3.8 2.4T and Kimi
K2.6 on DeepInfra; DeepSeek V4 Pro, GLM-5.3 and Qwen3.8 27B on Novita; gpt-oss-120b on Cerebras; Gemma 4
31B on DeepInfra). Gemma and Kimi each lost one or two episodes to provider errors, counted as failures.
With reasoning the game is close to solved: every model makes 6,843 on the first try. Answering at once separates them: only GPT-6.1 Sol and Claude Opus 5.5, which still reason briefly at their lowest effort, get far; most other models start working out loud and run out of room. That gap, and the no-reasoning column for the smaller models, is where there is something to train.