pyyol 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,66 +0,0 @@
1
- """Goofspiel agent — 2 players, 13 rounds, simultaneous bidding.
2
-
3
- Read references/games/goofspiel.md first. Replace `decide_card`; leave the rest.
4
-
5
- pyyol login && pyyol dev --matches 5
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from typing import List, Tuple
11
-
12
- from _shared import MatchMemory
13
- from pyyol import Adapter
14
- from pyyol.models import GoofspielMove, GoofspielView
15
-
16
-
17
- class GoofspielAgent(Adapter):
18
- name = "atlas-goofspiel"
19
- supported_games = ["goofspiel"]
20
-
21
- def __init__(self) -> None:
22
- self.mem = MatchMemory()
23
-
24
- def step(self, view: GoofspielView) -> GoofspielMove:
25
- safe = min(view.legal_actions) if view.legal_actions else 1
26
-
27
- if self.mem.already_answered(view.match_id, view.round):
28
- return GoofspielMove(round=view.round, card=safe, rationale="replayed turn")
29
-
30
- try:
31
- card, why = self.decide_card(view)
32
- except Exception as e: # noqa: BLE001 — never let the deadline decide
33
- return GoofspielMove(round=view.round, card=safe, rationale=f"fallback: {e}")
34
-
35
- # The model will occasionally name a card you do not hold. Sending it is
36
- # recorded as YOUR illegal move.
37
- if card not in view.legal_actions:
38
- card, why = safe, f"model chose an illegal card; {why}"
39
-
40
- return GoofspielMove(round=view.round, card=card, rationale=why[:200])
41
-
42
- # --- your strategy -----------------------------------------------------
43
-
44
- def decide_card(self, view: GoofspielView) -> Tuple[int, str]:
45
- """Return (card, one-line reason).
46
-
47
- Bid against `prize_pool`, not `current_prize` — ties carry.
48
- """
49
- opp = self.opponent_hand(view)
50
- threat = max(opp) if opp else 0
51
- beats = [c for c in view.legal_actions if c > threat]
52
-
53
- # Win by ONE. Pips saved on cheap prizes buy the expensive ones later.
54
- if beats and view.prize_pool >= 7:
55
- return min(beats), f"pool {view.prize_pool}: cheapest card over {threat}"
56
- return min(view.legal_actions), f"pool {view.prize_pool}: conceding cheaply"
57
-
58
- @staticmethod
59
- def opponent_hand(view: GoofspielView) -> List[int]:
60
- """Exactly what they still hold — identical starting hands mean their played
61
- cards tell you the rest."""
62
- spent = {r["opp_card"] for r in (view.history or []) if r.get("opp_card")}
63
- return [c for c in range(1, 14) if c not in spent]
64
-
65
-
66
- agent = GoofspielAgent()
@@ -1,77 +0,0 @@
1
- """Mafia agent — 12 seats, hidden roles, phase machine.
2
-
3
- Read references/games/mafia.md first. Note the view uses `legal`, NOT `legal_actions`,
4
- and `day`/`phase` rather than `round`.
5
-
6
- Replace `decide`; leave the rest.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import Tuple
12
-
13
- from _shared import MatchMemory
14
- from pyyol import Adapter
15
- from pyyol.models import MafiaMove, MafiaView
16
-
17
-
18
- class MafiaAgent(Adapter):
19
- name = "atlas-mafia"
20
- supported_games = ["mafia"]
21
-
22
- def __init__(self) -> None:
23
- self.mem = MatchMemory()
24
-
25
- def step(self, view: MafiaView) -> MafiaMove:
26
- # `legal` — not `legal_actions`. Getting this wrong means every move is
27
- # rejected and the engine plays for you.
28
- legal = view.legal or []
29
- if not legal:
30
- return MafiaMove(action="", rationale="nothing legal this phase")
31
- fallback = legal[0]
32
-
33
- # A turn is (day, phase) here — there is no round number.
34
- if self.mem.already_answered(view.match_id, (view.day, view.phase)):
35
- return MafiaMove(action=fallback, rationale="replayed turn")
36
-
37
- try:
38
- action, target, text, why = self.decide(view)
39
- except Exception as e: # noqa: BLE001
40
- return MafiaMove(action=fallback, rationale=f"fallback: {e}")
41
-
42
- if action not in legal:
43
- action, target, text, why = fallback, -1, "", f"illegal action; {why}"
44
-
45
- # target stays -1 when unused: seat 0 is a REAL player, so a forgotten target
46
- # would otherwise silently act on them.
47
- return MafiaMove(action=action, target=target, text=text[:400], rationale=why[:200])
48
-
49
- # --- your strategy -----------------------------------------------------
50
-
51
- def decide(self, view: MafiaView) -> Tuple[str, int, str, str]:
52
- """Return (action, target, text, reason).
53
-
54
- `view.public` is the table transcript; `view.private` carries what only you
55
- know (a Detective's finding arrives there and nowhere else). Rebuild your
56
- read of each seat from them every turn.
57
- """
58
- notes = self.mem.get(view.match_id)["notes"]
59
- living = [s for s, ok in (view.alive or {}).items() if ok and s != view.your_seat]
60
- suspect = max(living, key=lambda s: notes.get(s, 0), default=-1)
61
-
62
- if "message" in (view.legal or []):
63
- return (
64
- "message",
65
- -1,
66
- f"Seat {suspect} has been quiet. Thoughts?",
67
- "opening a line on the current suspect",
68
- )
69
- if "vote" in (view.legal or []) and suspect >= 0:
70
- return "vote", suspect, "", f"voting {suspect}, my standing read"
71
- for act in ("investigate", "protect", "night_kill", "profile"):
72
- if act in (view.legal or []) and suspect >= 0:
73
- return act, suspect, "", f"{act} on {suspect}"
74
- return (view.legal or [""])[0], -1, "", "no better option this phase"
75
-
76
-
77
- agent = MafiaAgent()
@@ -1,82 +0,0 @@
1
- """Monopoly agent — 2–8 players, board, near-perfect information.
2
-
3
- Read references/games/monopoly.md first. Drive off `phase` + `legal_actions`; the
4
- board lives in the raw `state` dict.
5
-
6
- Replace `decide`; leave the rest.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import Any, Dict, Tuple
12
-
13
- from _shared import MatchMemory
14
- from pyyol import Adapter
15
- from pyyol.models import MonopolyMove, MonopolyView
16
-
17
-
18
- class MonopolyAgent(Adapter):
19
- name = "atlas-monopoly"
20
- supported_games = ["monopoly"]
21
-
22
- def __init__(self) -> None:
23
- self.mem = MatchMemory()
24
-
25
- def step(self, view: MonopolyView) -> MonopolyMove:
26
- legal = view.legal_actions or []
27
- if not legal:
28
- return MonopolyMove(action="", rationale="nothing legal this phase")
29
- fallback = "end_turn" if "end_turn" in legal else legal[0]
30
-
31
- # Monopoly has no round number — the phase plus the board's turn counter is
32
- # the closest thing, so key on both.
33
- turn_no = (view.state or {}).get("turn", 0)
34
- if self.mem.already_answered(view.match_id, (turn_no, view.phase)):
35
- return MonopolyMove(action=fallback, rationale="replayed turn")
36
-
37
- try:
38
- action, prop, amount, why = self.decide(view)
39
- except Exception as e: # noqa: BLE001
40
- return MonopolyMove(action=fallback, rationale=f"fallback: {e}")
41
-
42
- if action not in legal:
43
- action, prop, amount, why = fallback, 0, 0, f"illegal action; {why}"
44
-
45
- return MonopolyMove(action=action, property=prop, amount=amount, rationale=why[:200])
46
-
47
- # --- your strategy -----------------------------------------------------
48
-
49
- def decide(self, view: MonopolyView) -> Tuple[str, int, int, str]:
50
- """Return (action, property, amount, reason).
51
-
52
- Read `phase` for the situation and `legal_actions` for what is allowed —
53
- do not assume fixed field names in `state`.
54
- """
55
- legal = view.legal_actions or []
56
- me: Dict[str, Any] = (view.state or {}).get("players", {}).get(str(view.seat), {})
57
- cash = int(me.get("cash", 0) or 0)
58
-
59
- if view.phase == "acquire" and "buy" in legal:
60
- # Keep a reserve: bankruptcy is the only true loss condition, and it is
61
- # usually caused by buying into a rent spike.
62
- if cash > 400:
63
- return "buy", 0, 0, f"buying with {cash} cash in hand"
64
- return (
65
- "decline" if "decline" in legal else legal[0],
66
- 0,
67
- 0,
68
- f"declining, only {cash} cash",
69
- )
70
-
71
- if view.phase == "auction" and "pass" in legal:
72
- return "pass", 0, 0, "not overpaying at auction"
73
-
74
- if view.phase == "roll" and "roll" in legal:
75
- return "roll", 0, 0, "rolling"
76
-
77
- if "end_turn" in legal:
78
- return "end_turn", 0, 0, "nothing worth doing this phase"
79
- return legal[0], 0, 0, "first legal action"
80
-
81
-
82
- agent = MonopolyAgent()