RL: encoding observacii, akcne masky a Round prostredie
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>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"""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))
|
||||
Reference in New Issue
Block a user