Zaklad RL vrstvy (rl/DESIGN.md): egocentricky rotovana observacia (ruka, videne karty, tipy, kopka, dedukovane voidy -- 233 dim), masky legalnych tipov/kariet zrkadliace pravidla enginu a RoundEnv (jedno kolo = jedna self-play epizoda). Fuzz-testy vynucuju zhodu masiek s enginom. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
71 lines
2.7 KiB
Python
71 lines
2.7 KiB
Python
"""Self-play prostredie nad jednym `Round`-om (viz rl/DESIGN.md).
|
|
|
|
Jedno kolo = jedna epizoda. Prostredie je multi-agentne a tahove: v kazdom
|
|
kroku je na tahu prave jeden hrac (`Decision.player`), akciu zan doda
|
|
volajuci (zdielana siet, heuristika, ...). Odmena je sparse a terminalna --
|
|
`Round.get_points_summary()` pre vsetkych 4 hracov naraz na konci kola.
|
|
|
|
Akcie: v tipovacej faze index tipu 0..8, v hracej faze index karty 0..31
|
|
(kanonicke cislovanie z rl/encoding.py). Legalne akcie urcuje
|
|
`Decision.mask`; nelegalna akcia prebuble ako BridzikException z enginu.
|
|
"""
|
|
|
|
from random import Random
|
|
from typing import NamedTuple
|
|
|
|
from bridzik import Round, ROUNDS_PER_SERIES
|
|
from rl.encoding import encode_observation, guess_mask, index_card, play_mask
|
|
|
|
PHASE_GUESS = 'guess'
|
|
PHASE_PLAY = 'play'
|
|
|
|
|
|
class Decision(NamedTuple):
|
|
"""Jeden rozhodovaci bod: kto je na tahu, v akej faze, co vidi a co smie."""
|
|
player: int
|
|
phase: str
|
|
obs: list
|
|
mask: list
|
|
|
|
|
|
class RoundEnv:
|
|
def __init__(self, rng: Random = None):
|
|
self.rng = rng if rng is not None else Random()
|
|
self.round = None
|
|
|
|
def reset(self, round_number: int = None, first_player: int = None) -> Decision:
|
|
"""Zacne novu epizodu; nezadane parametre sa sampluju uniformne."""
|
|
if round_number is None:
|
|
round_number = self.rng.randrange(ROUNDS_PER_SERIES)
|
|
if first_player is None:
|
|
first_player = self.rng.randrange(4)
|
|
self.round = Round(round_number, first_player, shuffler=self.rng.shuffle)
|
|
return self._decision()
|
|
|
|
def step(self, action: int):
|
|
"""Vykona akciu hraca na tahu.
|
|
|
|
Vracia (decision, rewards, done): pocas kola (Decision, None, False),
|
|
na konci kola (None, [body 4 hracov], True).
|
|
"""
|
|
if self.round is None or self.round.is_completed():
|
|
raise RuntimeError('Epizoda nebezi -- najprv zavolaj reset().')
|
|
player = self.round.get_active_player()
|
|
if not self.round.is_guessing_completed():
|
|
self.round.add_player_guess(player, action)
|
|
else:
|
|
self.round.play_card(player, index_card(action))
|
|
if self.round.is_completed():
|
|
return None, self.round.get_points_summary(), True
|
|
return self._decision(), None, False
|
|
|
|
def _decision(self) -> Decision:
|
|
player = self.round.get_active_player()
|
|
if not self.round.is_guessing_completed():
|
|
return Decision(player, PHASE_GUESS,
|
|
encode_observation(self.round, player),
|
|
guess_mask(self.round))
|
|
return Decision(player, PHASE_PLAY,
|
|
encode_observation(self.round, player),
|
|
play_mask(self.round, player))
|