diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2024-12-27 12:40:19 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-01-27 16:43:58 +0100 |
| commit | 4e2ab55d345fd5e5c5d2d5bc56948611b15564d5 (patch) | |
| tree | c6998ac54687c442ad0baa541edd0fbf917b1308 /go/game/state.go | |
| parent | 5aa8e7ddbc41aed55e7918e6afaec0b5c07fa510 (diff) | |
| download | muhqs-game-4e2ab55d345fd5e5c5d2d5bc56948611b15564d5.tar.gz muhqs-game-4e2ab55d345fd5e5c5d2d5bc56948611b15564d5.zip | |
introduce game.State interface abstraction
This allows to not use the internal game state directly from client
code.
Diffstat (limited to 'go/game/state.go')
| -rw-r--r-- | go/game/state.go | 296 |
1 files changed, 189 insertions, 107 deletions
diff --git a/go/game/state.go b/go/game/state.go index 078815da..cf7b7111 100644 --- a/go/game/state.go +++ b/go/game/state.go @@ -2,6 +2,7 @@ package game import ( "fmt" + "image/color" "log" "math/rand" "strconv" @@ -12,16 +13,34 @@ import ( "muhq.space/muhqs-game/go/utils" ) -type State struct { - Stores []*Store - Exile *PileOfCardsBase - Map *Map - Stack *Stack - Players []*Player - ActivePlayer *Player - ActivePhase PhaseType - Permanents []Permanent - Units []*Unit +type State interface { + Players() []*Player + PlayerByName(name string) *Player + PlayerById(id int) *Player + AddNewPlayer(name string, deck *Deck, color color.Color) + AddNewAiPlayer(name string, color color.Color) + ActivePlayer() *Player + ActivePhase() PhaseType + SetMap(m *Map) + Map() *Map + Stores() []*Store + Stack() *Stack + Exile() PileOfCards + Permanents() []Permanent + Units() []*Unit + Loop() []*Player +} + +type LocalState struct { + stores []*Store + exile *PileOfCardsBase + _map *Map + stack *Stack + players []*Player + activePlayerId int + activePhase PhaseType + permanents []Permanent + units []*Unit Rand *rand.Rand eotEffects []effect outstandingEquipment []*Card @@ -29,41 +48,104 @@ type State struct { triggers []Trigger } -func NewState() *State { - s := &State{} - s.Stack = NewStack(s) - s.Exile = NewPileOfCards() +func (s *LocalState) Players() []*Player { + return s.players +} + +func (s *LocalState) PlayerById(id int) *Player { + return s.players[id-1] +} + +func (s *LocalState) PlayerByName(name string) *Player { + for _, p := range s.players { + if p.Name == name { + return p + } + } + + return nil +} + +func (s *LocalState) ActivePlayer() *Player { + return s.players[s.activePlayerId] +} + +func (s *LocalState) ActivePhase() PhaseType { + return s.activePhase +} + +func (s *LocalState) Map() *Map { + return s._map +} + +func (s *LocalState) Stores() []*Store { + return s.stores +} + +func (s *LocalState) SetMap(m *Map) { + s._map = m +} + +func (s *LocalState) Exile() PileOfCards { + return s.exile +} + +func (s *LocalState) Stack() *Stack { + return s.stack +} + +func (s *LocalState) Permanents() []Permanent { + return s.permanents +} + +func (s *LocalState) Units() []*Unit { + return s.units +} + +func NewLocalState() *LocalState { + s := &LocalState{} + s.stack = NewStack(s) + s.exile = NewPileOfCards() s.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) return s } -func (s *State) AddNewPlayer(name string, deck *Deck) { - p := NewPlayer(len(s.Players)+1, name, deck, s) - s.Players = append(s.Players, p) +func (s *LocalState) AddNewPlayer(name string, deck *Deck, color color.Color) { + p := NewPlayer(len(s.players)+1, name, deck, s, color) + s.players = append(s.players, p) +} + +func (s *LocalState) AddNewAiPlayer(name string, color color.Color) { + switch name { + case "kraken": + s.AddKraken(color) + default: + log.Fatalf("Ai Player %s not implemented", name) + } } -func (s *State) IsActivePlayer(p *Player) bool { - return s.ActivePlayer == p +func (s *LocalState) IsActivePlayer(p *Player) bool { + return s.activePlayerId == p.Id } -func (s *State) Loop() []*Player { +func (s *LocalState) Loop() []*Player { winners := []*Player{} - if s.Map.HasStores() { + if s._map.HasStores() { poc := NewPileOfCards() - for _, p := range s.Players { + for _, p := range s.players { if p.IsKraken() { continue } p.Store.MoveInto(poc) } - s.Map.distributeStoreCards(poc, s.Rand) + s._map.distributeStoreCards(poc, s.Rand) } for len(winners) == 0 { - for _, p := range s.Players { - s.ActivePlayer = p + for _, p := range s.players { + s.activePlayerId = p.Id p.Turn++ if p.Deck.IsEmpty() { @@ -72,44 +154,44 @@ func (s *State) Loop() []*Player { p.Deck.Shuffle(s.Rand) } - s.ActivePhase = Phases.DrawPhase + s.activePhase = Phases.DrawPhase p.Draw() - s.ActivePhase = Phases.UpkeepPhase + s.activePhase = Phases.UpkeepPhase p.upkeep() s.phaseChange() - s.ActivePhase = Phases.ActionPhase + s.activePhase = Phases.ActionPhase p.actionPhase() s.phaseChange() - s.ActivePhase = Phases.BuyPhase + s.activePhase = Phases.BuyPhase p.buyPhase() s.phaseChange() - s.ActivePhase = Phases.DiscardPhase + s.activePhase = Phases.DiscardPhase p.discardPhase() s.endOfTurn() - winners = s.Map.WinCondition(s) + winners = s._map.WinCondition(s) } } return winners } -func (s *State) addTrigger(trigger Trigger) { +func (s *LocalState) addTrigger(trigger Trigger) { s.triggers = append(s.triggers, trigger) } -func (s *State) removeTriggers(triggers []Trigger) { +func (s *LocalState) removeTriggers(triggers []Trigger) { for _, t := range triggers { s.triggers = utils.RemoveFromUnorderedSlice(s.triggers, t) } } -func (s *State) handleTriggers() { +func (s *LocalState) handleTriggers() { var actions []*TriggeredAction var triggersToRemove []Trigger @@ -138,8 +220,8 @@ func (s *State) handleTriggers() { } } -func (s *State) stateBasedActions() { - for _, p := range s.Permanents { +func (s *LocalState) stateBasedActions() { + for _, p := range s.permanents { p.Card().Impl.stateBasedActions(s, p) if p.IsDestroyed() { @@ -150,8 +232,8 @@ func (s *State) stateBasedActions() { s.handleTriggers() } -func (s *State) IsValidPlay(a *PlayAction) error { - if s.ActivePhase != Phases.ActionPhase { +func (s *LocalState) IsValidPlay(a *PlayAction) error { + if s.activePhase != Phases.ActionPhase { return fmt.Errorf("Play actions can only be declared during one's actions phase") } @@ -171,15 +253,15 @@ func (s *State) IsValidPlay(a *PlayAction) error { return nil } -func (s *State) IsValidMove(a *MoveAction) error { - if s.ActivePhase != Phases.ActionPhase { +func (s *LocalState) IsValidMove(a *MoveAction) error { + if s.activePhase != Phases.ActionPhase { return fmt.Errorf("Move actions can only be declared during one's actions phase") } u := a.Source().(*Unit) tile := relaxedTileTarget(a, a.Target().sel[0]) - if !slices.Contains(u.MoveRangeTiles(s.Map), tile) { + if !slices.Contains(u.MoveRangeTiles(s._map), tile) { return fmt.Errorf("Unit %s@%v can not move to %s", u.card.Name, u.tile.Position, tile.Position.String()) } @@ -192,8 +274,8 @@ func (s *State) IsValidMove(a *MoveAction) error { return nil } -func (s *State) IsValidAttack(a *AttackAction) error { - if s.ActivePhase != Phases.ActionPhase { +func (s *LocalState) IsValidAttack(a *AttackAction) error { + if s.activePhase != Phases.ActionPhase { return fmt.Errorf("Attack actions can only be declared during one's actions phase") } @@ -212,13 +294,13 @@ func (s *State) IsValidAttack(a *AttackAction) error { return nil } -func (s *State) isValidBuy(a *BuyAction) error { +func (s *LocalState) isValidBuy(a *BuyAction) error { card := a.card() - if s.Map.HasStores() { + if s._map.HasStores() { var storeTiles []*Tile - for pos, store := range s.Map.Stores { + for pos, store := range s._map.Stores { if slices.Contains(store.cards, card) { - storeTiles = append(storeTiles, s.Map.TileAt(pos)) + storeTiles = append(storeTiles, s._map.TileAt(pos)) } } @@ -248,8 +330,8 @@ func (s *State) isValidBuy(a *BuyAction) error { return nil } -func (s *State) IsValidEquip(a *EquipAction) error { - if s.ActivePhase != Phases.ActionPhase { +func (s *LocalState) IsValidEquip(a *EquipAction) error { + if s.activePhase != Phases.ActionPhase { return fmt.Errorf("Play actions can only be declared during one's actions phase") } @@ -262,8 +344,8 @@ func (s *State) IsValidEquip(a *EquipAction) error { return nil } -func (s *State) IsValidArtifactSwitch(a *ArtifactSwitchAction) error { - if s.ActivePhase != Phases.ActionPhase { +func (s *LocalState) IsValidArtifactSwitch(a *ArtifactSwitchAction) error { + if s.activePhase != Phases.ActionPhase { return fmt.Errorf("Play actions can only be declared during one's actions phase") } @@ -283,8 +365,8 @@ func (s *State) IsValidArtifactSwitch(a *ArtifactSwitchAction) error { return nil } -func (s *State) IsValidArtifactMove(a *ArtifactMoveAction) error { - if s.ActivePhase != Phases.ActionPhase { +func (s *LocalState) IsValidArtifactMove(a *ArtifactMoveAction) error { + if s.activePhase != Phases.ActionPhase { return fmt.Errorf("Play actions can only be declared during one's actions phase") } @@ -304,7 +386,7 @@ func (s *State) IsValidArtifactMove(a *ArtifactMoveAction) error { return nil } -func (s *State) ValidateAction(a Action) (err error) { +func (s *LocalState) ValidateAction(a Action) (err error) { err = a.CheckTargets(s) if err != nil { return @@ -329,15 +411,15 @@ func (s *State) ValidateAction(a Action) (err error) { return } -func (s *State) pushAction(a Action) { +func (s *LocalState) pushAction(a Action) { if !a.PayCosts(s) { log.Panicf("Cost for %s could not be paid", a) } - s.Stack.push(a) + s.stack.push(a) } -func (s *State) declareAction(a Action) { +func (s *LocalState) declareAction(a Action) { var err error err = a.CheckTargets(s) @@ -345,12 +427,12 @@ func (s *State) declareAction(a Action) { if err != nil { switch a.(type) { case *BuyAction: - if s.ActivePhase != Phases.BuyPhase { + if s.activePhase != Phases.BuyPhase { err = fmt.Errorf("Cards can only be bought during one's buy phase") } case *FreeAction: default: - if !s.Stack.IsEmpty() { + if a.Speed() == ActionSpeeds.Slow && !s.stack.IsEmpty() { err = fmt.Errorf("Slow action can not be declared while other actions are on the stack") } } @@ -370,7 +452,7 @@ func (s *State) declareAction(a Action) { } } -func (s *State) orderTriggeredActions(triggeredActions []*TriggeredAction) []Action { +func (s *LocalState) orderTriggeredActions(triggeredActions []*TriggeredAction) []Action { playerActions := make(map[*Player][]*TriggeredAction) for _, action := range triggeredActions { p := action.Controller() @@ -382,9 +464,9 @@ func (s *State) orderTriggeredActions(triggeredActions []*TriggeredAction) []Act } orderedActions := []Action{} - nPlayers := len(s.Players) + nPlayers := len(s.players) for i := 0; i < nPlayers; i++ { - p := s.Players[(i+s.ActivePlayer.Id-1)%nPlayers] + p := s.players[(i+s.activePlayerId-1)%nPlayers] actions := playerActions[p] pActions := make([]Action, 0, len(actions)) @@ -409,14 +491,14 @@ func (s *State) orderTriggeredActions(triggeredActions []*TriggeredAction) []Act return orderedActions } -func (s *State) allPassing(skipFirst bool) bool { - nPlayers := len(s.Players) +func (s *LocalState) allPassing(skipFirst bool) bool { + nPlayers := len(s.players) i := 0 if skipFirst { i++ } for ; i < nPlayers; i++ { - p := s.Players[(i+s.ActivePlayer.Id-1)%nPlayers] + p := s.players[(i+s.activePlayerId-1)%nPlayers] s.stateBasedActions() p.Ctrl.SendNotification(newPriorityNotification()) a := p.Ctrl.RecvAction() @@ -431,25 +513,25 @@ func (s *State) allPassing(skipFirst bool) bool { return true } -func (s *State) phaseChange() { +func (s *LocalState) phaseChange() { skipFirst := true for !s.allPassing(skipFirst) { - s.Stack.resolve() + s.stack.resolve() skipFirst = false } } -func (s *State) broadcastNotification(n PlayerNotification) { - for _, p := range s.Players { +func (s *LocalState) broadcastNotification(n PlayerNotification) { + for _, p := range s.players { p.Ctrl.SendNotification(n) } } -func (s *State) addPermanent(perm Permanent) { - s.Permanents = append(s.Permanents, perm) +func (s *LocalState) addPermanent(perm Permanent) { + s.permanents = append(s.permanents, perm) switch p := perm.(type) { case *Unit: - s.Units = append(s.Units, p) + s.units = append(s.units, p) } if perm.Tile() != nil { @@ -459,29 +541,29 @@ func (s *State) addPermanent(perm Permanent) { perm.Card().Impl.onETB(s, perm) } -func (s *State) addNewUnit(card *Card, pos Position, owner *Player) *Unit { - u := NewUnit(card, s.Map.TileAt(pos), owner) +func (s *LocalState) addNewUnit(card *Card, pos Position, owner *Player) *Unit { + u := NewUnit(card, s._map.TileAt(pos), owner) s.addPermanent(u) return u } -func (s *State) addNewArtifact(card *Card, pos Position, owner *Player) *Artifact { - a := NewArtifact(card, s.Map.TileAt(pos), owner) +func (s *LocalState) addNewArtifact(card *Card, pos Position, owner *Player) *Artifact { + a := NewArtifact(card, s._map.TileAt(pos), owner) s.addPermanent(a) return a } -func (s *State) addNewEquipment(card *Card, pos Position, owner *Player) *Equipment { - e := newEquipment(card, s.Map.TileAt(pos), owner) +func (s *LocalState) addNewEquipment(card *Card, pos Position, owner *Player) *Equipment { + e := newEquipment(card, s._map.TileAt(pos), owner) s.addPermanent(e) return e } -func (s *State) removePermanent(p Permanent) { - for i, perm := range s.Permanents { +func (s *LocalState) removePermanent(p Permanent) { + for i, perm := range s.permanents { if perm == p { - s.Permanents[i] = s.Permanents[len(s.Permanents)-1] - s.Permanents = s.Permanents[:len(s.Permanents)-1] + s.permanents[i] = s.permanents[len(s.permanents)-1] + s.permanents = s.permanents[:len(s.permanents)-1] } } } @@ -496,7 +578,7 @@ func removePtr[P *Unit](collection []P, ptr P) []P { return collection } -func (s *State) destroyPermanent(p Permanent) { +func (s *LocalState) destroyPermanent(p Permanent) { t := TileOrContainingPermTile(p) _leaveTile(p) @@ -504,7 +586,7 @@ func (s *State) destroyPermanent(p Permanent) { switch p := p.(type) { case *Unit: - s.Units = removePtr(s.Units, p) + s.units = removePtr(s.units, p) } if pile := p.Pile(); len(pile) > 0 { @@ -519,12 +601,12 @@ func (s *State) destroyPermanent(p Permanent) { s.events = append(s.events, Event{eventType: EventTypes.Destruction, affected: []interface{}{p}}) } -func (s *State) fight(p1, p2 Permanent) { +func (s *LocalState) fight(p1, p2 Permanent) { p1.fight(p2) p2.fight(p1) } -func (s *State) switchPermanents(p1 Permanent, p2 Permanent) { +func (s *LocalState) switchPermanents(p1 Permanent, p2 Permanent) { t1 := TileOrContainingPermTile(p1) t2 := TileOrContainingPermTile(p2) @@ -535,9 +617,9 @@ func (s *State) switchPermanents(p1 Permanent, p2 Permanent) { enterTileOrPile(p2, t1) } -func (s *State) FilterUnits(filter func(*Unit) bool) []*Unit { +func (s *LocalState) FilterUnits(filter func(*Unit) bool) []*Unit { units := []*Unit{} - for _, u := range s.Units { + for _, u := range s.units { if filter(u) { units = append(units, u) } @@ -545,19 +627,19 @@ func (s *State) FilterUnits(filter func(*Unit) bool) []*Unit { return units } -func (s *State) OwnUnits(player *Player) []*Unit { +func (s *LocalState) OwnUnits(player *Player) []*Unit { return s.FilterUnits(func(u *Unit) bool { return u.Controller() == player }) } -func (s *State) EnemyUnits(player *Player) []*Unit { +func (s *LocalState) EnemyUnits(player *Player) []*Unit { return s.FilterUnits(func(u *Unit) bool { return u.Controller().IsEnemy(player) }) } -func (s *State) resolvePlay(a *PlayAction) { +func (s *LocalState) resolvePlay(a *PlayAction) { p := a.Controller() c := a.Card targets := a.targets @@ -588,14 +670,14 @@ func (s *State) resolvePlay(a *PlayAction) { } if c.Type == CardTypes.Spell { - if !s.Exile.Contains(c) { + if !s.exile.Contains(c) { p.DiscardPile.AddCard(c) } } } -func (s *State) additionalSpawnsFor(p *Player, cType CardType) (spawns []*Tile) { - for _, perm := range s.Permanents { +func (s *LocalState) additionalSpawnsFor(p *Player, cType CardType) (spawns []*Tile) { + for _, perm := range s.permanents { if perm.Controller() != p { continue } @@ -604,8 +686,8 @@ func (s *State) additionalSpawnsFor(p *Player, cType CardType) (spawns []*Tile) return } -func (s *State) SpawnTiles(p *Player) []*Tile { - spawns := s.Map.FilterTiles(func(t *Tile) bool { +func (s *LocalState) SpawnTiles(p *Player) []*Tile { + spawns := s._map.FilterTiles(func(t *Tile) bool { if t.Type != TileTypes.Spawn { return false } @@ -616,12 +698,12 @@ func (s *State) SpawnTiles(p *Player) []*Tile { return spawns } -func (s *State) WaterSpawnTiles(p *Player) []*Tile { +func (s *LocalState) WaterSpawnTiles(p *Player) []*Tile { if p.IsKraken() { return s.KrakenSpawnTiles() } - docks := s.Map.FilterTiles(func(t *Tile) bool { + docks := s._map.FilterTiles(func(t *Tile) bool { if t.Type != TileTypes.Docks { return false } @@ -631,7 +713,7 @@ func (s *State) WaterSpawnTiles(p *Player) []*Tile { spawns := []*Tile{} for _, dock := range docks { - for _, candidate := range TilesInRangeFromOrigin(s.Map, dock.Position, 1) { + for _, candidate := range TilesInRangeFromOrigin(s._map, dock.Position, 1) { if candidate.Water { spawns = append(spawns, candidate) } @@ -641,7 +723,7 @@ func (s *State) WaterSpawnTiles(p *Player) []*Tile { return spawns } -func (s *State) AvailableSpawnTiles(p *Player, c *Card) []*Tile { +func (s *LocalState) AvailableSpawnTiles(p *Player, c *Card) []*Tile { if !c.IsPermanent() { log.Panicf("AvailableSpawnTiles called for %s", c.Type.String()) } @@ -669,27 +751,27 @@ func (s *State) AvailableSpawnTiles(p *Player, c *Card) []*Tile { return tiles } -func (s *State) redistributeMapStoreCards() { +func (s *LocalState) redistributeMapStoreCards() { poc := NewPileOfCards() - for _, store := range s.Map.Stores { + for _, store := range s._map.Stores { store.MoveInto(poc) } - s.Map.distributeStoreCards(poc, s.Rand) + s._map.distributeStoreCards(poc, s.Rand) - for _, p := range s.Players { + for _, p := range s.players { p.clearKnownStore() } } -func (s *State) addEotEffect(e effect) { +func (s *LocalState) addEotEffect(e effect) { e.apply(s) s.eotEffects = append(s.eotEffects, e) } -func (s *State) endOfTurn() { +func (s *LocalState) endOfTurn() { // Move all outstanding equipment cards to the discard pile for _, e := range s.outstandingEquipment { - s.ActivePlayer.DiscardPile.AddCard(e) + s.ActivePlayer().DiscardPile.AddCard(e) } for _, e := range s.eotEffects { |
