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)]