shortdeck1.0

This commit is contained in:
2025-09-30 18:09:49 +08:00
commit ee95b8e049
24 changed files with 532 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

41
shortdeck_arena/agent.py Normal file
View File

@@ -0,0 +1,41 @@
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .simulation import Simulation
class Agent:
def __init__(self, pid: int):
self.pid = pid
def try_act(self, sim: 'Simulation'):
return None
def __str__(self):
return f"Agent({self.pid})"
class HumanAgent(Agent):
def try_act(self, sim: 'Simulation'):
return None
def __str__(self):
return "HumanAgent"
class RandomAgent(Agent):
def try_act(self, sim: 'Simulation'):
import random
info = sim.node_info()
choices = ["fold", "call"]
if info.get("bet_max", 0) > 0:
choices.append("bet")
action = random.choice(choices)
if action == "bet":
amt = random.randint(max(1, info.get("bet_min", 1)), info.get("bet_max", 1))
sim.apply_action(self.pid, "bet", amt)
else:
sim.apply_action(self.pid, action)

52
shortdeck_arena/card.py Normal file
View File

@@ -0,0 +1,52 @@
"""ShortDeck card model (6-A, 36 cards)."""
from __future__ import annotations
from enum import IntEnum
from typing import List
class Suit(IntEnum):
S = 0
H = 1
D = 2
C = 3
def __str__(self) -> str:
return "shdc"[self.value]
class Rank(IntEnum):
R6 = 6
R7 = 7
R8 = 8
R9 = 9
RT = 10
RJ = 11
RQ = 12
RK = 13
RA = 14
def __str__(self):
if self.value <= 9:
return str(self.value)
return {10: "T", 11: "J", 12: "Q", 13: "K", 14: "A"}[self.value]
class Card:
def __init__(self, rank: Rank, suit: Suit):
self.rank = rank
self.suit = suit
def __repr__(self) -> str:
return f"{str(self.rank)}{str(self.suit)}"
def __str__(self) -> str:
return self.__repr__()
@classmethod
def all_short(cls) -> List["Card"]:
cards: List[Card] = []
for r in [Rank.R6, Rank.R7, Rank.R8, Rank.R9, Rank.RT, Rank.RJ, Rank.RQ, Rank.RK, Rank.RA]:
for s in Suit:
cards.append(Card(r, s))
return cards

23
shortdeck_arena/main.py Normal file
View File

@@ -0,0 +1,23 @@
from __future__ import annotations
from pathlib import Path
from .agent import HumanAgent, RandomAgent
from .simulation import Simulation
def main():
agents = [HumanAgent(0), RandomAgent(1)]
sim = Simulation(agents)
print("Player cards:")
for i in range(len(agents)):
print(i, sim.player_cards(i))
print("Random agent acting...")
agents[1].try_act(sim)
print("History:", sim.history)
sim.dump_data(Path.cwd() / "shortdeck_arena_history.jsonl")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,65 @@
from __future__ import annotations
import json
import random
from pathlib import Path
from typing import List, Dict, Optional
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .agent import Agent
from .card import Card
class Simulation:
def __init__(self, agents: List[Agent]):
self.agents = agents
self.history: List[Dict] = []
self.cards: List[Card] = []
self.saved = False
self.new_round()
def new_round(self):
self.history = []
self.cards = Card.all_short()
random.shuffle(self.cards)
self.saved = False
def player_cards(self, pid: int) -> List[Card]:
return self.cards[pid * 2 : pid * 2 + 2]
def board_cards(self, street: str) -> List[Card]:
nplayers = len(self.agents)
idx_start = nplayers * 2
if street == "flop":
return self.cards[idx_start: idx_start + 3]
if street == "turn":
return self.cards[idx_start: idx_start + 4]
if street == "river":
return self.cards[idx_start: idx_start + 5]
return []
def node_info(self) -> Dict:
return {"bet_min": 1, "bet_max": 100}
def apply_action(self, pid: int, action: str, amount: Optional[int] = None):
self.history.append({"pid": pid, "action": action, "amount": amount})
def to_save_data(self) -> Dict:
players = [f"Agent{a.pid}" for a in self.agents]
return {
"history": self.history,
"players": players,
"player_cards": ["".join(str(c) for c in self.player_cards(i)) for i in range(len(self.agents))],
"board": "".join(str(c) for c in self.board_cards("river")),
}
def dump_data(self, path: Path | None = None):
if self.saved:
return
if path is None:
path = Path.cwd() / "shortdeck_arena_history.jsonl"
with path.open("a", encoding="utf-8") as f:
f.write(json.dumps(self.to_save_data(), ensure_ascii=False))
f.write("\n")
self.saved = True