File size: 4,600 Bytes
a7ffb22 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | """The twelve comets (PRD CMT-3) and the rule for when one crosses (CMT-1). The server decides both: the browser only draws."""
import random
from datetime import date
COMETS = [
{"id": "pi", "sym": "ฯ", "name": "ืคืื", "fact": "ืืืืก ืืื ืืืืงืฃ ืฉื ืืขืื ืืงืืืจ ืฉืื. ืืกืคืจืืช ืฉืื ืืืจื ืื ืงืืื ืื ื ืืืจืืช ืืื ืืืืจืืช ืขื ืขืฆืื."},
{"id": "fib", "sym": "1,1,2,3,5", "name": "ืคืืืื ืืฆืณื", "fact": "ืื ืืกืคืจ ืืกืืจื ืืื ืกืืื ืฉื ื ืืงืืืืื. ืืกืืจื ืืืคืืขื ืืกืคืืจืืืช ืฉื ืืจืขืื ื ืืื ืืื ืืืืฆืืจืืืืื."},
{"id": "phi", "sym": "ฯ", "name": "ืืืก ืืืื", "fact": "ืืขืจื 1.618. ืืืืงืื ืงืืข ืื ืฉืืืืก ืืื ืืฉืื ืืืืง ืืืืื ืฉืืื ืืืืก ืืื ืืืืื ืืงืื. ืืืคืืข ืืคืจืืื, ืืฆืืคืื ืืืืื ืืช."},
{"id": "inf", "sym": "โ", "name": "ืืื ืกืืฃ", "fact": "ืืฉ ืืื ืกืืฃ ืืกืคืจืื ืฉืืืื, ืืืฉ ืื ืืื ืกืืฃ ืืกืคืจืื ืืื 0 ืึพ1. ืืชืืจืจ ืฉืืืื ืกืืฃ ืืฉื ื ืืืื ืืืชืจ."},
{"id": "e", "sym": "e", "name": "ืืืกืคืจ e", "fact": "ืืขืจื 2.718. ืืืคืืข ืืื ืืงืื ืฉืื ืืฉืื ืืื ืืงืฆื ืฉืชืืื ืืืืื ืฉืื: ืจืืืืช, ืืืืืืกืืืช, ืืืืืงืื."},
{"id": "r2", "sym": "โ2", "name": "ืฉืืจืฉ 2", "fact": "ืืืืจื ืฉื ืืืืกืื ืืจืืืืข ืฉืฆืืขื 1. ืืืจ ืืืืื ืื ืืงืืืื ืื ืืืืืื ืฉืื ืืคืฉืจ ืืืชืื ืืืชื ืืฉืืจ."},
{"id": "zero", "sym": "0", "name": "ืืคืก", "fact": "ืืืฉื ืืืคื ืฉื ืื ืื ืืื ืกืืื ืืืคืก. ืืื ืืืืข ืืืืื, ืืืืืืชื ืืคืฉืจ ืืืชืื ืื ืืกืคืจ ืืขืฉืจ ืกืคืจืืช ืืืื."},
{"id": "pyth", "sym": "aยฒ+bยฒ=cยฒ", "name": "ืคืืชืืืจืก", "fact": "ืืืฉืืืฉ ืืฉืจ ืืืืืช, ืกืืื ืจืืืืขื ืื ืืฆืืื ืฉืืื ืืจืืืืข ืืืชืจ. ืืืืข ืืืืชืจ ืึพ370 ืืืืืืช ืฉืื ืืช."},
{"id": "prime", "sym": "2,3,5,7", "name": "ืืกืคืจืื ืจืืฉืื ืืื", "fact": "ืืกืคืจืื ืฉืืชืืืงืื ืจืง ืึพ1 ืืืขืฆืื. ืืฉ ืืื ืกืืฃ ืืืื, ืืขืืืื ืื ืืื ืืืฆืคื ื ืฉื ืืืื ืืจื ื."},
{"id": "pascal", "sym": "1 2 1", "name": "ืืฉืืืฉ ืคืกืงื", "fact": "ืื ืืกืคืจ ืืื ืกืืื ืฉื ื ืืืกืคืจืื ืฉืืขืืื. ืืฉืืจืืช ืฉืื ื ืืชื ืืช ืืช ืืืงืืืื ืฉื (a + b) ืืื ืืืงื."},
{"id": "euler", "sym": "VโE+F=2", "name": "ื ืืกืืช ืืืืืจ", "fact": "ืืื ืืืฃ ืืฉืืืื, ืงืืืงืืืื ืคืืืช ืืงืฆืืขืืช ืืขืื ืคืืืช ืฉืืื ืชืืื 2. ื ืกื ืขื ืงืืืืื: 8 โ 12 + 6."},
{"id": "mobius", "sym": "โ", "name": "ืืืขืช ืืืืืก", "fact": "ืจืฆืืขืช ื ืืืจ ืขื ืืฆื ืกืืืื: ืืฉ ืื ืจืง ืฆื ืืื ืืฉืคื ืืืช. ื ืืื ืฉืืืืืช ืขืืื ืชืขืืืจ ืืฉื ื ืืดืฆืืืืืด."},
]
IDS = [c["id"] for c in COMETS]
CHANCE = 0.12 # a right answer's chance of a comet, once the student has been offered their first
def get(comet_id: str) -> dict | None:
return next((c for c in COMETS if c["id"] == comet_id), None)
def due(state: dict | None, caught: set[str], today: date, goal_just_reached: bool, roll: float | None = None) -> bool:
"""Is a comet due after this right answer? At most one a day; none once all are caught; the first one only on the answer that
reaches the daily goal; after any comet was offered, a small chance on each right answer. `roll` (0..1) is injectable for tests."""
if len(caught) >= len(COMETS):
return False
last = (state or {}).get("last_comet_day")
if last == today:
return False
if last is None:
return goal_just_reached
return (random.random() if roll is None else roll) < CHANCE
def surely_not(state: dict | None, today: date, goal_just_reached: bool, roll: float | None) -> bool:
"""True when no comet can be due whatever the student has caught, so the caught list need not be read (the usual case)."""
last = (state or {}).get("last_comet_day")
if last == today:
return True
if last is None:
return not goal_just_reached
return (random.random() if roll is None else roll) >= CHANCE
def pick(caught: set[str], roll: float | None = None) -> dict | None:
"""A comet the student does not have yet, at random."""
left = [c for c in COMETS if c["id"] not in caught]
if not left:
return None
return left[int((random.random() if roll is None else roll) * len(left)) % len(left)]
|