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