Spaces:
Running
Running
Rename to Scott Wu Math; add eval results; mobile layout fixes
Browse files- Dockerfile +5 -5
- README.md +47 -10
- __init__.py +3 -3
- client.py +4 -4
- models.py +1 -1
- openenv.yaml +2 -2
- pyproject.toml +6 -6
- scripts/build_tasks.py +4 -4
- server/app.py +4 -4
- server/environment.py +2 -2
- server/web/app.css +3 -1
- server/web/app.js +1 -1
- server/web/card.html +2 -2
- server/web/index.html +2 -2
Dockerfile
CHANGED
|
@@ -1,14 +1,14 @@
|
|
| 1 |
-
#
|
| 2 |
-
# docker build -t
|
| 3 |
FROM python:3.12-slim
|
| 4 |
|
| 5 |
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PORT=7860
|
| 6 |
RUN useradd -m -u 1000 user
|
| 7 |
WORKDIR /home/user/app
|
| 8 |
|
| 9 |
-
COPY --chown=user . ./
|
| 10 |
-
RUN pip install --no-cache-dir ./
|
| 11 |
|
| 12 |
USER user
|
| 13 |
EXPOSE 7860
|
| 14 |
-
CMD ["sh", "-c", "uvicorn
|
|
|
|
| 1 |
+
# Scott Wu Math: OpenEnv server + play page. Pure Python, no GPU.
|
| 2 |
+
# docker build -t scott-wu-math . && docker run -p 7860:7860 scott-wu-math
|
| 3 |
FROM python:3.12-slim
|
| 4 |
|
| 5 |
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PORT=7860
|
| 6 |
RUN useradd -m -u 1000 user
|
| 7 |
WORKDIR /home/user/app
|
| 8 |
|
| 9 |
+
COPY --chown=user . ./scott_wu_math
|
| 10 |
+
RUN pip install --no-cache-dir ./scott_wu_math
|
| 11 |
|
| 12 |
USER user
|
| 13 |
EXPOSE 7860
|
| 14 |
+
CMD ["sh", "-c", "uvicorn scott_wu_math.server.app:app --host 0.0.0.0 --port ${PORT} --proxy-headers --forwarded-allow-ips='*'"]
|
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
emoji: 🃏
|
| 4 |
colorFrom: green
|
| 5 |
colorTo: gray
|
|
@@ -7,7 +7,7 @@ sdk: docker
|
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
license: apache-2.0
|
| 10 |
-
short_description:
|
| 11 |
tags:
|
| 12 |
- openenv
|
| 13 |
- reinforcement-learning
|
|
@@ -15,12 +15,15 @@ tags:
|
|
| 15 |
- reasoning
|
| 16 |
---
|
| 17 |
|
| 18 |
-
#
|
| 19 |
|
| 20 |
Deal some playing cards, name a number, and make that number from the cards with + − × ÷, using every
|
| 21 |
card exactly once. People and language models play the same environment: the page at `/` drives the
|
| 22 |
same OpenEnv session over `/ws` that an agent does.
|
| 23 |
|
|
|
|
|
|
|
|
|
|
| 24 |
## Where it comes from
|
| 25 |
|
| 26 |
In a clip filmed at a pub, Cognition's CEO Scott Wu explains the game he plays with a deck of cards.
|
|
@@ -92,9 +95,9 @@ more than getting it right.
|
|
| 92 |
## Use it
|
| 93 |
|
| 94 |
```python
|
| 95 |
-
from
|
| 96 |
|
| 97 |
-
with
|
| 98 |
obs = env.reset(split="video", index=1).observation
|
| 99 |
print(obs.prompt)
|
| 100 |
result = env.step(CardAction(expression="(((12 - 10) * (6 + 4) * 10 + 8) * 11 - 7) * 3"))
|
|
@@ -104,10 +107,10 @@ with CardArithmeticEnv(base_url="https://fineenvs-cardarithmetic.hf.space").sync
|
|
| 104 |
Over plain WebSocket (what the play page does): connect to `/ws`, send
|
| 105 |
`{"type": "reset", "data": {"level": "four-digit"}}`, then `{"type": "step", "data": {"expression": "..."}}`.
|
| 106 |
|
| 107 |
-
Task discovery follows OpenEnv's Task API: `GET /
|
| 108 |
with `{"split": "test"}`, and so on. Task listings leave out the reference solutions.
|
| 109 |
|
| 110 |
-
For text-only RL (no server), `
|
| 111 |
`extract_answer(completion)` are the same grader as a plain function, and `game.prompts.messages(cards, target)`
|
| 112 |
builds the same prompt.
|
| 113 |
|
|
@@ -125,15 +128,49 @@ non-negative number, the way people play; a second pass allows fractions.
|
|
| 125 |
|
| 126 |
```bash
|
| 127 |
pip install -e .
|
| 128 |
-
uvicorn
|
| 129 |
python scripts/build_tasks.py # rebuild data/tasks (deterministic)
|
| 130 |
pytest
|
| 131 |
```
|
| 132 |
|
| 133 |
## Evaluations
|
| 134 |
|
| 135 |
-
`eval/rollout.py` in the [FineEnvs repository](https://github.com/adithya-s-k/FineEnvs/tree/main/12-
|
| 136 |
runs OpenAI, Anthropic and open models on Hugging Face Inference Providers against a running server, in
|
| 137 |
parallel, one OpenEnv session per episode.
|
| 138 |
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Scott Wu Math
|
| 3 |
emoji: 🃏
|
| 4 |
colorFrom: green
|
| 5 |
colorTo: gray
|
|
|
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
license: apache-2.0
|
| 10 |
+
short_description: Scott Wu's card game as an OpenEnv RL environment
|
| 11 |
tags:
|
| 12 |
- openenv
|
| 13 |
- reinforcement-learning
|
|
|
|
| 15 |
- reasoning
|
| 16 |
---
|
| 17 |
|
| 18 |
+
# Scott Wu Math
|
| 19 |
|
| 20 |
Deal some playing cards, name a number, and make that number from the cards with + − × ÷, using every
|
| 21 |
card exactly once. People and language models play the same environment: the page at `/` drives the
|
| 22 |
same OpenEnv session over `/ws` that an agent does.
|
| 23 |
|
| 24 |
+
Named after the game Scott Wu plays in the clip below, as a fan tribute. It is not affiliated with or
|
| 25 |
+
endorsed by Scott Wu or Cognition.
|
| 26 |
+
|
| 27 |
## Where it comes from
|
| 28 |
|
| 29 |
In a clip filmed at a pub, Cognition's CEO Scott Wu explains the game he plays with a deck of cards.
|
|
|
|
| 95 |
## Use it
|
| 96 |
|
| 97 |
```python
|
| 98 |
+
from scott_wu_math import CardAction, ScottWuMathEnv
|
| 99 |
|
| 100 |
+
with ScottWuMathEnv(base_url="https://fineenvs-scottwumath.hf.space").sync() as env:
|
| 101 |
obs = env.reset(split="video", index=1).observation
|
| 102 |
print(obs.prompt)
|
| 103 |
result = env.step(CardAction(expression="(((12 - 10) * (6 + 4) * 10 + 8) * 11 - 7) * 3"))
|
|
|
|
| 107 |
Over plain WebSocket (what the play page does): connect to `/ws`, send
|
| 108 |
`{"type": "reset", "data": {"level": "four-digit"}}`, then `{"type": "step", "data": {"expression": "..."}}`.
|
| 109 |
|
| 110 |
+
Task discovery follows OpenEnv's Task API: `GET /scott_wu_math/splits`, `POST /scott_wu_math/tasks`
|
| 111 |
with `{"split": "test"}`, and so on. Task listings leave out the reference solutions.
|
| 112 |
|
| 113 |
+
For text-only RL (no server), `scott_wu_math.game.reward.grade(expression, card_values, target)` and
|
| 114 |
`extract_answer(completion)` are the same grader as a plain function, and `game.prompts.messages(cards, target)`
|
| 115 |
builds the same prompt.
|
| 116 |
|
|
|
|
| 128 |
|
| 129 |
```bash
|
| 130 |
pip install -e .
|
| 131 |
+
uvicorn scott_wu_math.server.app:app --port 8012 # play at http://localhost:8012
|
| 132 |
python scripts/build_tasks.py # rebuild data/tasks (deterministic)
|
| 133 |
pytest
|
| 134 |
```
|
| 135 |
|
| 136 |
## Evaluations
|
| 137 |
|
| 138 |
+
`eval/rollout.py` in the [FineEnvs repository](https://github.com/adithya-s-k/FineEnvs/tree/main/12-scott-wu-math)
|
| 139 |
runs OpenAI, Anthropic and open models on Hugging Face Inference Providers against a running server, in
|
| 140 |
parallel, one OpenEnv session per episode.
|
| 141 |
|
| 142 |
+
### Results (2026-10-23)
|
| 143 |
+
|
| 144 |
+
Twelve models on the first five `test` deals of each level (20 deals, one sample each) and on the two
|
| 145 |
+
`video` hands (three samples each), in three modes:
|
| 146 |
+
|
| 147 |
+
- **Reasoning**: the model's own reasoning at high effort, up to 32k output tokens.
|
| 148 |
+
- **No reasoning**: reasoning switched off, or as low as the API allows (`*`), up to 32k tokens, so the
|
| 149 |
+
model may still work it out in its visible reply.
|
| 150 |
+
- **At once**: no reasoning, a system line asking for the answer tag alone, and 300 output tokens: the
|
| 151 |
+
mental-arithmetic version of the game.
|
| 152 |
+
|
| 153 |
+
| Model | Reasoning | No reasoning | At once | Video hands: reasoning / none / at once |
|
| 154 |
+
|---|---|---|---|---|
|
| 155 |
+
| GPT-6.1 Sol | 20/20 | 20/20* | 14/20* | 6/6 / 6/6 / 3/6 |
|
| 156 |
+
| Claude Opus 5.5 | 20/20 | 19/20* | 8/20* | 6/6 / 6/6 / 5/6 |
|
| 157 |
+
| GLM-5.3 | 17/20 | 19/20 | 5/20 | 6/6 / 5/6 / 0/6 |
|
| 158 |
+
| Qwen3.8 2.4T | 20/20 | 19/20 | 4/20 | 6/6 / 5/6 / 0/6 |
|
| 159 |
+
| Claude Sonnet 5.5 | 20/20 | 15/20 | 4/20 | 6/6 / 4/6 / 0/6 |
|
| 160 |
+
| Claude Haiku 5.5 | 20/20 | 11/20 | 3/20 | 6/6 / 5/6 / 0/6 |
|
| 161 |
+
| Kimi K2.6 | 19/20 | 20/20 | 2/20 | 6/6 / 6/6 / 0/6 |
|
| 162 |
+
| DeepSeek V4 Pro | 20/20 | 10/20 | 2/20 | 6/6 / 3/6 / 0/6 |
|
| 163 |
+
| gpt-oss-120b | 16/20 | 6/20* | 2/20* | 6/6 / 1/6 / 0/6 |
|
| 164 |
+
| GPT-6 Luna | 20/20 | 2/20 | 2/20 | 5/6 / 0/6 / 0/6 |
|
| 165 |
+
| Qwen3.8 27B | 18/20 | 20/20 | 1/20 | 6/6 / 6/6 / 0/6 |
|
| 166 |
+
| Gemma 4 31B | 19/20 | 18/20 | 1/20 | 5/6 / 5/6 / 0/6 |
|
| 167 |
+
|
| 168 |
+
`*` lowest effort the API allows: GPT-6.1 Sol and gpt-oss-120b at `low`, Claude Opus 5.5 at `low` (its
|
| 169 |
+
thinking cannot be disabled). Open models ran on Hugging Face Inference Providers (Qwen3.8 2.4T and Kimi
|
| 170 |
+
K2.6 on DeepInfra; DeepSeek V4 Pro, GLM-5.3 and Qwen3.8 27B on Novita; gpt-oss-120b on Cerebras; Gemma 4
|
| 171 |
+
31B on DeepInfra). Gemma and Kimi each lost one or two episodes to provider errors, counted as failures.
|
| 172 |
+
|
| 173 |
+
With reasoning the game is close to solved: every model makes 6,843 on the first try. Answering at once
|
| 174 |
+
separates them: only GPT-6.1 Sol and Claude Opus 5.5, which still reason briefly at their lowest effort,
|
| 175 |
+
get far; most other models start working out loud and run out of room. That gap, and the no-reasoning
|
| 176 |
+
column for the smaller models, is where there is something to train.
|
__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
-
"""
|
| 2 |
|
| 3 |
-
from .client import
|
| 4 |
from .models import CardAction, CardObservation, CardState
|
| 5 |
|
| 6 |
-
__all__ = ["CardAction", "
|
|
|
|
| 1 |
+
"""Scott Wu Math: an OpenEnv environment for mental arithmetic with playing cards."""
|
| 2 |
|
| 3 |
+
from .client import ScottWuMathEnv
|
| 4 |
from .models import CardAction, CardObservation, CardState
|
| 5 |
|
| 6 |
+
__all__ = ["CardAction", "ScottWuMathEnv", "CardObservation", "CardState"]
|
client.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
-
"""Client for a running
|
| 2 |
|
| 3 |
-
from
|
| 4 |
|
| 5 |
-
with
|
| 6 |
obs = env.reset(level="four-digit", seed=7).observation
|
| 7 |
print(obs.prompt)
|
| 8 |
result = env.step(CardAction(expression="..."))
|
|
@@ -19,7 +19,7 @@ from openenv.core.env_client import EnvClient
|
|
| 19 |
from .models import CardAction, CardObservation, CardState
|
| 20 |
|
| 21 |
|
| 22 |
-
class
|
| 23 |
def _step_payload(self, action: CardAction) -> Dict[str, Any]:
|
| 24 |
return action.model_dump(exclude={"metadata"})
|
| 25 |
|
|
|
|
| 1 |
+
"""Client for a running Scott Wu Math server (local, Docker or a Hugging Face Space).
|
| 2 |
|
| 3 |
+
from scott_wu_math import CardAction, ScottWuMathEnv
|
| 4 |
|
| 5 |
+
with ScottWuMathEnv(base_url="http://localhost:8012").sync() as env:
|
| 6 |
obs = env.reset(level="four-digit", seed=7).observation
|
| 7 |
print(obs.prompt)
|
| 8 |
result = env.step(CardAction(expression="..."))
|
|
|
|
| 19 |
from .models import CardAction, CardObservation, CardState
|
| 20 |
|
| 21 |
|
| 22 |
+
class ScottWuMathEnv(EnvClient[CardAction, CardObservation, CardState]):
|
| 23 |
def _step_payload(self, action: CardAction) -> Dict[str, Any]:
|
| 24 |
return action.model_dump(exclude={"metadata"})
|
| 25 |
|
models.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Action, observation and state of the
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
|
|
| 1 |
+
"""Action, observation and state of the Scott Wu Math environment."""
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
openenv.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
spec_version: 1
|
| 2 |
-
name:
|
| 3 |
type: space
|
| 4 |
runtime: fastapi
|
| 5 |
-
app:
|
| 6 |
port: 8000
|
|
|
|
| 1 |
spec_version: 1
|
| 2 |
+
name: scott_wu_math
|
| 3 |
type: space
|
| 4 |
runtime: fastapi
|
| 5 |
+
app: scott_wu_math.server.app:app
|
| 6 |
port: 8000
|
pyproject.toml
CHANGED
|
@@ -3,7 +3,7 @@ requires = ["setuptools>=61", "wheel"]
|
|
| 3 |
build-backend = "setuptools.build_meta"
|
| 4 |
|
| 5 |
[project]
|
| 6 |
-
name = "
|
| 7 |
version = "0.1.0"
|
| 8 |
description = "OpenEnv environment: make a target number from playing cards with + - * /, every card once"
|
| 9 |
requires-python = ">=3.11"
|
|
@@ -19,16 +19,16 @@ dev = ["pytest>=8.0.0", "httpx>=0.27.0", "websockets>=12.0"]
|
|
| 19 |
eval = ["openai>=1.50.0", "python-dotenv>=1.0.0"]
|
| 20 |
|
| 21 |
[project.scripts]
|
| 22 |
-
|
| 23 |
|
| 24 |
[tool.setuptools]
|
| 25 |
include-package-data = true
|
| 26 |
-
packages = ["
|
| 27 |
-
package-dir = { "
|
| 28 |
|
| 29 |
[tool.setuptools.package-data]
|
| 30 |
-
|
| 31 |
-
"
|
| 32 |
|
| 33 |
[tool.pytest.ini_options]
|
| 34 |
testpaths = ["tests"]
|
|
|
|
| 3 |
build-backend = "setuptools.build_meta"
|
| 4 |
|
| 5 |
[project]
|
| 6 |
+
name = "scott-wu-math"
|
| 7 |
version = "0.1.0"
|
| 8 |
description = "OpenEnv environment: make a target number from playing cards with + - * /, every card once"
|
| 9 |
requires-python = ">=3.11"
|
|
|
|
| 19 |
eval = ["openai>=1.50.0", "python-dotenv>=1.0.0"]
|
| 20 |
|
| 21 |
[project.scripts]
|
| 22 |
+
scott-wu-math-server = "scott_wu_math.server.app:main"
|
| 23 |
|
| 24 |
[tool.setuptools]
|
| 25 |
include-package-data = true
|
| 26 |
+
packages = ["scott_wu_math", "scott_wu_math.server", "scott_wu_math.game"]
|
| 27 |
+
package-dir = { "scott_wu_math" = ".", "scott_wu_math.server" = "server", "scott_wu_math.game" = "game" }
|
| 28 |
|
| 29 |
[tool.setuptools.package-data]
|
| 30 |
+
scott_wu_math = ["data/tasks/*.jsonl", "README.md", "openenv.yaml"]
|
| 31 |
+
"scott_wu_math.server" = ["web/*"]
|
| 32 |
|
| 33 |
[tool.pytest.ini_options]
|
| 34 |
testpaths = ["tests"]
|
scripts/build_tasks.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"""Build the fixed task splits in data/tasks/.
|
| 2 |
|
| 3 |
-
python -m
|
| 4 |
|
| 5 |
test: 50 deals per level (200), seed 2026
|
| 6 |
train: 1,000 deals per level (4,000), seed 7, none sharing an id with test
|
|
@@ -16,9 +16,9 @@ from pathlib import Path
|
|
| 16 |
|
| 17 |
HERE = Path(__file__).resolve().parents[1]
|
| 18 |
sys.path.insert(0, str(HERE.parent))
|
| 19 |
-
from
|
| 20 |
-
from
|
| 21 |
-
from
|
| 22 |
|
| 23 |
OUT = HERE / "data" / "tasks"
|
| 24 |
|
|
|
|
| 1 |
"""Build the fixed task splits in data/tasks/.
|
| 2 |
|
| 3 |
+
python -m scott_wu_math.scripts.build_tasks # or: python scripts/build_tasks.py from the env dir
|
| 4 |
|
| 5 |
test: 50 deals per level (200), seed 2026
|
| 6 |
train: 1,000 deals per level (4,000), seed 7, none sharing an id with test
|
|
|
|
| 16 |
|
| 17 |
HERE = Path(__file__).resolve().parents[1]
|
| 18 |
sys.path.insert(0, str(HERE.parent))
|
| 19 |
+
from scott_wu_math.game.cards import Card # noqa: E402
|
| 20 |
+
from scott_wu_math.game.expr import parse, steps, to_text # noqa: E402
|
| 21 |
+
from scott_wu_math.game.tasks import LEVELS, make_task # noqa: E402
|
| 22 |
|
| 23 |
OUT = HERE / "data" / "tasks"
|
| 24 |
|
server/app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"""FastAPI app: the OpenEnv API (/reset, /step, /state, /ws, /schema, task routes) plus the play page at /.
|
| 2 |
|
| 3 |
-
uvicorn
|
| 4 |
"""
|
| 5 |
|
| 6 |
from __future__ import annotations
|
|
@@ -13,7 +13,7 @@ from fastapi.staticfiles import StaticFiles
|
|
| 13 |
from openenv.core.env_server.http_server import create_app
|
| 14 |
|
| 15 |
from ..models import CardAction, CardObservation, CardState
|
| 16 |
-
from .environment import
|
| 17 |
|
| 18 |
WEB = Path(__file__).resolve().parent / "web"
|
| 19 |
README = Path(__file__).resolve().parents[1] / "README.md"
|
|
@@ -22,10 +22,10 @@ README = Path(__file__).resolve().parents[1] / "README.md"
|
|
| 22 |
os.environ["ENABLE_WEB_INTERFACE"] = "false"
|
| 23 |
|
| 24 |
app = create_app(
|
| 25 |
-
|
| 26 |
CardAction,
|
| 27 |
CardObservation,
|
| 28 |
-
env_name="
|
| 29 |
max_concurrent_envs=int(os.getenv("MAX_CONCURRENT_ENVS", "256")),
|
| 30 |
state_cls=CardState,
|
| 31 |
)
|
|
|
|
| 1 |
"""FastAPI app: the OpenEnv API (/reset, /step, /state, /ws, /schema, task routes) plus the play page at /.
|
| 2 |
|
| 3 |
+
uvicorn scott_wu_math.server.app:app --port 8012
|
| 4 |
"""
|
| 5 |
|
| 6 |
from __future__ import annotations
|
|
|
|
| 13 |
from openenv.core.env_server.http_server import create_app
|
| 14 |
|
| 15 |
from ..models import CardAction, CardObservation, CardState
|
| 16 |
+
from .environment import ScottWuMathEnvironment
|
| 17 |
|
| 18 |
WEB = Path(__file__).resolve().parent / "web"
|
| 19 |
README = Path(__file__).resolve().parents[1] / "README.md"
|
|
|
|
| 22 |
os.environ["ENABLE_WEB_INTERFACE"] = "false"
|
| 23 |
|
| 24 |
app = create_app(
|
| 25 |
+
ScottWuMathEnvironment,
|
| 26 |
CardAction,
|
| 27 |
CardObservation,
|
| 28 |
+
env_name="scott_wu_math",
|
| 29 |
max_concurrent_envs=int(os.getenv("MAX_CONCURRENT_ENVS", "256")),
|
| 30 |
state_cls=CardState,
|
| 31 |
)
|
server/environment.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""
|
| 2 |
|
| 3 |
One episode is one deal and one graded submission. `reset` deals (from a split of the task set,
|
| 4 |
from a level, or for a custom target or hand); `step` takes the final expression, grades it once,
|
|
@@ -36,7 +36,7 @@ def load_split(split: str) -> list[dict]:
|
|
| 36 |
return _TASKS[split]
|
| 37 |
|
| 38 |
|
| 39 |
-
class
|
| 40 |
SUPPORTS_CONCURRENT_SESSIONS = True
|
| 41 |
|
| 42 |
def __init__(self) -> None:
|
|
|
|
| 1 |
+
"""Scott Wu Math: deal cards, name a target, make it with + - * / using every card once.
|
| 2 |
|
| 3 |
One episode is one deal and one graded submission. `reset` deals (from a split of the task set,
|
| 4 |
from a level, or for a custom target or hand); `step` takes the final expression, grades it once,
|
|
|
|
| 36 |
return _TASKS[split]
|
| 37 |
|
| 38 |
|
| 39 |
+
class ScottWuMathEnvironment(Environment[CardAction, CardObservation, CardState]):
|
| 40 |
SUPPORTS_CONCURRENT_SESSIONS = True
|
| 41 |
|
| 42 |
def __init__(self) -> None:
|
server/web/app.css
CHANGED
|
@@ -239,7 +239,9 @@ dialog p { font-size: 14px; }
|
|
| 239 |
.table { padding: 14px 12px; min-height: 0; }
|
| 240 |
.target { font-size: 28px; }
|
| 241 |
.piles { gap: 10px 8px; }
|
| 242 |
-
.pile { --cw: clamp(
|
|
|
|
|
|
|
| 243 |
.typed { flex-wrap: wrap; }
|
| 244 |
.typed label { flex-basis: 100%; }
|
| 245 |
.steps.compact { padding-left: 22px; }
|
|
|
|
| 239 |
.table { padding: 14px 12px; min-height: 0; }
|
| 240 |
.target { font-size: 28px; }
|
| 241 |
.piles { gap: 10px 8px; }
|
| 242 |
+
.pile { --per: min(var(--n, 8), 5); --cw: clamp(54px, calc((100cqw - (var(--per) - 1) * 8px) / var(--per)), 84px); }
|
| 243 |
+
.controls { display: grid; grid-template-columns: 1fr 1fr; }
|
| 244 |
+
.controls .grow { display: none; }
|
| 245 |
.typed { flex-wrap: wrap; }
|
| 246 |
.typed label { flex-basis: 100%; }
|
| 247 |
.steps.compact { padding-left: 22px; }
|
server/web/app.js
CHANGED
|
@@ -280,7 +280,7 @@
|
|
| 280 |
}
|
| 281 |
|
| 282 |
// ---- per-browser record --------------------------------------------------------
|
| 283 |
-
const STORE = "
|
| 284 |
function load() { try { return JSON.parse(localStorage.getItem(STORE)) || {}; } catch { return {}; } }
|
| 285 |
function record(task, solved, secs) {
|
| 286 |
const all = load(), key = task.task_id.startsWith("video-") ? "video" : task.level;
|
|
|
|
| 280 |
}
|
| 281 |
|
| 282 |
// ---- per-browser record --------------------------------------------------------
|
| 283 |
+
const STORE = "scott-wu-math-stats-v1";
|
| 284 |
function load() { try { return JSON.parse(localStorage.getItem(STORE)) || {}; } catch { return {}; } }
|
| 285 |
function record(task, solved, secs) {
|
| 286 |
const all = load(), key = task.task_id.startsWith("video-") ? "video" : task.level;
|
server/web/card.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
-
<title>
|
| 7 |
<link rel="stylesheet" href="/static/app.css">
|
| 8 |
<style>
|
| 9 |
.doc { max-width: 820px; margin: 0 auto; padding: 24px 24px 64px; }
|
|
@@ -23,7 +23,7 @@
|
|
| 23 |
</head>
|
| 24 |
<body>
|
| 25 |
<header class="bar">
|
| 26 |
-
<div class="brand"><a href="/" style="text-decoration:none">
|
| 27 |
<nav><a href="/">Play</a><a href="/static/card.html" aria-current="page">Environment card</a><a href="/docs">API</a></nav>
|
| 28 |
</header>
|
| 29 |
<article class="doc" id="doc">Loading…</article>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<title>Scott Wu Math · Environment card</title>
|
| 7 |
<link rel="stylesheet" href="/static/app.css">
|
| 8 |
<style>
|
| 9 |
.doc { max-width: 820px; margin: 0 auto; padding: 24px 24px 64px; }
|
|
|
|
| 23 |
</head>
|
| 24 |
<body>
|
| 25 |
<header class="bar">
|
| 26 |
+
<div class="brand"><a href="/" style="text-decoration:none">Scott Wu Math</a></div>
|
| 27 |
<nav><a href="/">Play</a><a href="/static/card.html" aria-current="page">Environment card</a><a href="/docs">API</a></nav>
|
| 28 |
</header>
|
| 29 |
<article class="doc" id="doc">Loading…</article>
|
server/web/index.html
CHANGED
|
@@ -3,14 +3,14 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
-
<title>
|
| 7 |
<meta name="description" content="Make the target from playing cards with + − × ÷, every card once. An OpenEnv environment people and language models play the same way.">
|
| 8 |
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect x='5' y='2' width='22' height='28' rx='3' fill='%23fff' stroke='%23222' stroke-width='2'/%3E%3Ctext x='16' y='22' font-size='14' text-anchor='middle' fill='%23c0392b'%3E%E2%99%A5%3C/text%3E%3C/svg%3E">
|
| 9 |
<link rel="stylesheet" href="/static/app.css">
|
| 10 |
</head>
|
| 11 |
<body>
|
| 12 |
<header class="bar">
|
| 13 |
-
<div class="brand">
|
| 14 |
<nav>
|
| 15 |
<a href="#rules" id="rules-link">Rules</a>
|
| 16 |
<a href="/static/card.html">Environment card</a>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<title>Scott Wu Math</title>
|
| 7 |
<meta name="description" content="Make the target from playing cards with + − × ÷, every card once. An OpenEnv environment people and language models play the same way.">
|
| 8 |
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect x='5' y='2' width='22' height='28' rx='3' fill='%23fff' stroke='%23222' stroke-width='2'/%3E%3Ctext x='16' y='22' font-size='14' text-anchor='middle' fill='%23c0392b'%3E%E2%99%A5%3C/text%3E%3C/svg%3E">
|
| 9 |
<link rel="stylesheet" href="/static/app.css">
|
| 10 |
</head>
|
| 11 |
<body>
|
| 12 |
<header class="bar">
|
| 13 |
+
<div class="brand">Scott Wu Math</div>
|
| 14 |
<nav>
|
| 15 |
<a href="#rules" id="rules-link">Rules</a>
|
| 16 |
<a href="/static/card.html">Environment card</a>
|