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 | |
| 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.
| -rw-r--r-- | go/client/game.go | 47 | ||||
| -rw-r--r-- | go/client/startMenu.go | 12 | ||||
| -rw-r--r-- | go/game/action.go | 149 | ||||
| -rw-r--r-- | go/game/ai.go | 12 | ||||
| -rw-r--r-- | go/game/areaEffect_test.go | 13 | ||||
| -rw-r--r-- | go/game/card.go | 28 | ||||
| -rw-r--r-- | go/game/cardImplementations.go | 74 | ||||
| -rw-r--r-- | go/game/cardParsing.go | 24 | ||||
| -rw-r--r-- | go/game/costs.go | 4 | ||||
| -rw-r--r-- | go/game/effect.go | 20 | ||||
| -rw-r--r-- | go/game/kraken.go | 31 | ||||
| -rw-r--r-- | go/game/map.go | 4 | ||||
| -rw-r--r-- | go/game/permanent.go | 4 | ||||
| -rw-r--r-- | go/game/player.go | 25 | ||||
| -rw-r--r-- | go/game/stack.go | 4 | ||||
| -rw-r--r-- | go/game/state.go | 296 | ||||
| -rw-r--r-- | go/game/targets.go | 50 | ||||
| -rw-r--r-- | go/game/targets_test.go | 15 | ||||
| -rw-r--r-- | go/game/tile.go | 2 | ||||
| -rw-r--r-- | go/game/trigger.go | 10 | ||||
| -rw-r--r-- | go/game/unit.go | 4 | ||||
| -rw-r--r-- | go/game/unit_test.go | 19 | ||||
| -rw-r--r-- | go/game/winCondition.go | 12 | ||||
| -rw-r--r-- | go/server/main.go | 2 | ||||
| -rw-r--r-- | go/ui/mapView.go | 40 | ||||
| -rw-r--r-- | go/ui/stateBar.go | 15 |
26 files changed, 514 insertions, 402 deletions
diff --git a/go/client/game.go b/go/client/game.go index 38a2971c..50f0400b 100644 --- a/go/client/game.go +++ b/go/client/game.go @@ -53,18 +53,19 @@ type Game struct { triggers []*game.TriggeredAction ui.Collection - activePlayer *game.Player + activePlayerId int playerCtrl *game.ChanPlayerControl hasPriority bool - gameState *game.State + gameState game.State storesOnMap bool } -func newGame(app *app) *Game { +func newGame(app *app, gameState game.State) *Game { g := &Game{ app: app, + gameState: gameState, keyBindings: make(keyBindings), Collection: ui.Collection{ Width: app.windowWidth, @@ -77,8 +78,6 @@ func newGame(app *app) *Game { g.keyBindings[ebiten.KeySpace] = pass g.keyBindings[ebiten.KeyEscape] = reset - g.gameState = game.NewState() - g.passButton = ui.NewSimpleButton(g.Width-PASS_BUTTON_WIDTH, g.Height-DEFAULT_BUTTON_HEIGHT, PASS_BUTTON_WIDTH, @@ -102,7 +101,7 @@ func (g *Game) loadMap(mapName string) *Game { if err != nil { log.Fatal(err) } - g.gameState.Map = m + g.gameState.SetMap(m) g.mapView = ui.NewMapView(g.gameState) g.AddWidget(g.mapView) @@ -113,24 +112,18 @@ func (g *Game) loadMap(mapName string) *Game { func (g *Game) addActivePlayer(name string, deckList string) *Game { deck := game.NewDeckFromDeckList(deckList) - color := playerColors[len(g.gameState.Players)] - g.gameState.AddNewPlayer(name, deck) - p := g.getPlayer(name) - p.Color = color + color := playerColors[len(g.gameState.Players())] + g.gameState.AddNewPlayer(name, deck, color) + p := g.gameState.PlayerByName(name) + g.activePlayerId = p.Id return g.initPlayerUi(p) } -func (g *Game) getPlayer(name string) *game.Player { - for _, p := range g.gameState.Players { - if p.Name == name { - return p - } - } - return nil +func (g *Game) activePlayer() *game.Player { + return g.gameState.PlayerById(g.activePlayerId) } func (g *Game) initPlayerUi(player *game.Player) *Game { - g.activePlayer = player g.playerCtrl = game.NewChanPlayerControl(player) player.Ctrl = g.playerCtrl var x, y int @@ -168,7 +161,7 @@ func (g *Game) initPlayerUi(player *game.Player) *Game { x = g.discardPileButton.X + g.discardPileButton.Width/2 y = g.Height - DEFAULT_BUTTON_HEIGHT - g.discardPileView = ui.NewPocList(x, y, g.activePlayer.DiscardPile) + g.discardPileView = ui.NewPocList(x, y, g.activePlayer().DiscardPile) if !g.storesOnMap { x = g.stateBar.Width + g.discardPileButton.Width @@ -188,7 +181,7 @@ func (g *Game) initPlayerUi(player *game.Player) *Game { x = g.storeButton.X + g.storeButton.Width/2 y = g.Height - DEFAULT_BUTTON_HEIGHT - g.storeView = ui.NewPocList(x, y, g.activePlayer.Store) + g.storeView = ui.NewPocList(x, y, g.activePlayer().Store) } return g @@ -406,7 +399,7 @@ func (g *Game) updatePassButton() { } func (g *Game) passPriority() { - g.declareAction(game.NewPassPriority(g.activePlayer)) + g.declareAction(game.NewPassPriority(g.activePlayer())) } func (g *Game) handlePlayerNotifications() { @@ -423,14 +416,14 @@ func (g *Game) handlePlayerNotifications() { } a := n.Context.(game.Action) g.stackBuffer.AddLine(a.String()) - if !g.gameState.Stack.IsEmpty() && g.FindWidget(g.stackBuffer) == -1 { + if !g.gameState.Stack().IsEmpty() && g.FindWidget(g.stackBuffer) == -1 { g.AddWidget(g.stackBuffer) } case game.ResolvedActionNotification: g.clearMapHighlights() g.stackBuffer.RemoveLast() - if g.gameState.Stack.IsEmpty() { + if g.gameState.Stack().IsEmpty() { g.RemoveWidget(g.stackBuffer) } g.mapView.ForceRedraw() @@ -494,13 +487,13 @@ func (g *Game) handleSelection(obj interface{}, x, y int) { switch obj := obj.(type) { case *game.Tile: if g.storesOnMap && obj.Type == game.TileTypes.Store && - g.activePlayer.KnowsStore(obj.Position) { + g.activePlayer().KnowsStore(obj.Position) { if g.storeView != nil { g.hideStore() } - g.storeView = ui.NewPocList(x, y, g.gameState.Map.StoreOn(obj.Position)) + g.storeView = ui.NewPocList(x, y, g.gameState.Map().StoreOn(obj.Position)) g.showStore() } @@ -516,13 +509,13 @@ func (g *Game) handleSelection(obj interface{}, x, y int) { 0, func(nc *ui.NumberChoice) { a := game.NewPlayActionVariadicCosts( - g.activePlayer, + g.activePlayer(), obj.C, nc.GetChoosen()) g.progressPlayAction(a) })) } else { - a := game.NewPlayAction(g.activePlayer, obj.C) + a := game.NewPlayAction(g.activePlayer(), obj.C) g.progressPlayAction(a) } diff --git a/go/client/startMenu.go b/go/client/startMenu.go index 94ec3aef..0fc7cbe7 100644 --- a/go/client/startMenu.go +++ b/go/client/startMenu.go @@ -3,7 +3,7 @@ package main import ( "log" - // "muhq.space/muhqs-game/go/game" + "muhq.space/muhqs-game/go/game" "muhq.space/muhqs-game/go/ui" ) @@ -132,17 +132,19 @@ func (m *StartMenu) Layout(width, height int) (int, int) { } func (m *StartMenu) startGame() { + var state game.State if m.remote != "" { log.Fatal("Remote games are currently not implemented") + } else { + state = game.NewLocalState() } - g := newGame(m.app).loadMap(m.mapPath).addActivePlayer(m.playerName, m.startDeck) + g := newGame(m.app, state).loadMap(m.mapPath).addActivePlayer(m.playerName, m.startDeck) if m.mapPath == "the-kraken" { - idx := len(g.gameState.Players) + idx := len(g.gameState.Players()) color := playerColors[idx] - g.gameState.AddKraken() - g.gameState.Players[idx].Color = color + g.gameState.AddNewAiPlayer("kraken", color) } m.app.pushActivity(g.Start()) diff --git a/go/game/action.go b/go/game/action.go index 5a8fc35b..715f39a3 100644 --- a/go/game/action.go +++ b/go/game/action.go @@ -5,55 +5,73 @@ import ( "log" ) +type ActionSpeed int + +const ( + slow ActionSpeed = iota + fast +) + +var ActionSpeeds = struct { + Slow ActionSpeed + Fast ActionSpeed +}{ + Slow: slow, + Fast: fast, +} + type ( - ActionCostFunc func(*State) bool - ActionResolveFunc func(*State) + ActionCostFunc func(*LocalState) bool + ActionResolveFunc func(*LocalState) ActionFuncPrototype func(Action) ActionResolveFunc ) type Action interface { Source() interface{} Controller() *Player - GameState() *State + Speed() ActionSpeed + GameState() *LocalState Targets() *Targets Target() *Target // Shortcut for to select the first target NeedsActionCostChoice() bool - CheckTargets(*State) error - PayCosts(s *State) bool - resolve(s *State) + CheckTargets(*LocalState) error + PayCosts(s *LocalState) bool + resolve(s *LocalState) String() string } type PassPriority struct{ player *Player } -func (a *PassPriority) Source() interface{} { return a.player } -func (a *PassPriority) Controller() *Player { return a.player } -func (a *PassPriority) GameState() *State { return a.player.gameState } -func (*PassPriority) Targets() *Targets { return nil } -func (*PassPriority) Target() *Target { return nil } -func (*PassPriority) NeedsActionCostChoice() bool { return false } -func (*PassPriority) CheckTargets(*State) error { return nil } -func (*PassPriority) PayCosts(*State) bool { return true } -func (*PassPriority) resolve(*State) {} -func (*PassPriority) String() string { return "pass" } -func NewPassPriority(p *Player) Action { return &PassPriority{p} } +func (a *PassPriority) Source() interface{} { return a.player } +func (a *PassPriority) Controller() *Player { return a.player } +func (a *PassPriority) Speed() ActionSpeed { return fast } +func (a *PassPriority) GameState() *LocalState { return a.player.gameState } +func (*PassPriority) Targets() *Targets { return nil } +func (*PassPriority) Target() *Target { return nil } +func (*PassPriority) NeedsActionCostChoice() bool { return false } +func (*PassPriority) CheckTargets(*LocalState) error { return nil } +func (*PassPriority) PayCosts(*LocalState) bool { return true } +func (*PassPriority) resolve(*LocalState) {} +func (*PassPriority) String() string { return "pass" } +func NewPassPriority(p *Player) Action { return &PassPriority{p} } type TargetSelection struct { player *Player targets *Targets } -func (sel *TargetSelection) Source() interface{} { return sel.player } -func (sel *TargetSelection) Controller() *Player { return sel.player } -func (sel *TargetSelection) GameState() *State { return sel.player.gameState } -func (sel *TargetSelection) Targets() *Targets { return sel.targets } -func (sel *TargetSelection) Target() *Target { return sel.targets.ts[0] } -func (*TargetSelection) NeedsActionCostChoice() bool { return false } -func (sel *TargetSelection) CheckTargets(s *State) error { return sel.targets.CheckTargets(s) } -func (*TargetSelection) PayCosts(*State) bool { return true } -func (*TargetSelection) resolve(*State) {} -func (*TargetSelection) String() string { return "target selection" } +func (sel *TargetSelection) Source() interface{} { return sel.player } +func (sel *TargetSelection) Controller() *Player { return sel.player } +func (sel *TargetSelection) Speed() ActionSpeed { return fast } +func (sel *TargetSelection) GameState() *LocalState { return sel.player.gameState } +func (sel *TargetSelection) Targets() *Targets { return sel.targets } +func (sel *TargetSelection) Target() *Target { return sel.targets.ts[0] } +func (*TargetSelection) NeedsActionCostChoice() bool { return false } +func (sel *TargetSelection) CheckTargets(s *LocalState) error { return sel.targets.CheckTargets(s) } +func (*TargetSelection) PayCosts(*LocalState) bool { return true } +func (*TargetSelection) resolve(*LocalState) {} +func (*TargetSelection) String() string { return "target selection" } func newTargetSelection(player *Player, targets *Targets) Action { return &TargetSelection{player, targets} @@ -86,9 +104,10 @@ func (a *ActionBase) Controller() *Player { return nil } } -func (a *ActionBase) GameState() *State { return a.Controller().gameState } -func (a *ActionBase) resolve(s *State) { a.resolveFunc(s) } -func (a *ActionBase) PayCosts(s *State) bool { +func (a *ActionBase) Speed() ActionSpeed { return slow } +func (a *ActionBase) GameState() *LocalState { return a.Controller().gameState } +func (a *ActionBase) resolve(s *LocalState) { a.resolveFunc(s) } +func (a *ActionBase) PayCosts(s *LocalState) bool { if a.costFunc != nil { return a.costFunc(s) } @@ -96,7 +115,7 @@ func (a *ActionBase) PayCosts(s *State) bool { } func (a *ActionBase) Targets() *Targets { return a.targets } func (a *ActionBase) Target() *Target { return a.targets.ts[0] } -func (a *ActionBase) CheckTargets(s *State) error { +func (a *ActionBase) CheckTargets(s *LocalState) error { if a.targets == nil { return nil } @@ -131,16 +150,17 @@ type DeclareTriggeredActionsAction struct { actions []*TriggeredAction } -func (sel *DeclareTriggeredActionsAction) Source() interface{} { return nil } -func (sel *DeclareTriggeredActionsAction) Controller() *Player { return nil } -func (sel *DeclareTriggeredActionsAction) GameState() *State { return nil } -func (sel *DeclareTriggeredActionsAction) Targets() *Targets { return nil } -func (sel *DeclareTriggeredActionsAction) Target() *Target { return nil } -func (*DeclareTriggeredActionsAction) NeedsActionCostChoice() bool { return false } -func (sel *DeclareTriggeredActionsAction) CheckTargets(s *State) error { return nil } -func (*DeclareTriggeredActionsAction) PayCosts(*State) bool { return true } -func (*DeclareTriggeredActionsAction) resolve(*State) {} -func (*DeclareTriggeredActionsAction) String() string { return "declared triggered actions" } +func (sel *DeclareTriggeredActionsAction) Source() interface{} { return nil } +func (sel *DeclareTriggeredActionsAction) Controller() *Player { return nil } +func (sel *DeclareTriggeredActionsAction) Speed() ActionSpeed { return fast } +func (sel *DeclareTriggeredActionsAction) GameState() *LocalState { return nil } +func (sel *DeclareTriggeredActionsAction) Targets() *Targets { return nil } +func (sel *DeclareTriggeredActionsAction) Target() *Target { return nil } +func (*DeclareTriggeredActionsAction) NeedsActionCostChoice() bool { return false } +func (sel *DeclareTriggeredActionsAction) CheckTargets(s *LocalState) error { return nil } +func (*DeclareTriggeredActionsAction) PayCosts(*LocalState) bool { return true } +func (*DeclareTriggeredActionsAction) resolve(*LocalState) {} +func (*DeclareTriggeredActionsAction) String() string { return "declared triggered actions" } func NewDeclareTriggeredActionsAction(t []*TriggeredAction) *DeclareTriggeredActionsAction { return &DeclareTriggeredActionsAction{t} @@ -166,7 +186,7 @@ func (a *PileDropAction) String() string { return s[:len(s)-1] + " ]" } -func (*PileDropAction) PayCosts(*State) bool { return true } +func (*PileDropAction) PayCosts(*LocalState) bool { return true } func newPileDropAction(perm Permanent, tile *Tile, pile []Permanent) *PileDropAction { a := &PileDropAction{ @@ -176,7 +196,7 @@ func newPileDropAction(perm Permanent, tile *Tile, pile []Permanent) *PileDropAc pile, } - a.resolveFunc = func(s *State) { + a.resolveFunc = func(s *LocalState) { for i, p := range pile { p.onDrop(p) p.setContainingPerm(nil) @@ -201,7 +221,7 @@ var ( ) func NewPlayActionCostFunc(player *Player, cost int) ActionCostFunc { - return func(*State) bool { + return func(*LocalState) bool { if player.Resource < cost { return false } @@ -242,7 +262,7 @@ func NewPlayAction(p *Player, c *Card, args ...interface{}) *PlayAction { a.targets = newTargets() } - a.resolveFunc = func(s *State) { + a.resolveFunc = func(s *LocalState) { s.resolvePlay(a) } @@ -255,6 +275,13 @@ func NewPlayActionVariadicCosts(p *Player, c *Card, variadicCosts int) *PlayActi return a } +func (a *PlayAction) Speed() ActionSpeed { + if a.Card.IsPermanent() { + return slow + } + return fast +} + func (a *PlayAction) String() string { p := a.source.(*Player) if a.targets.RequireSelection() { @@ -292,7 +319,7 @@ func NewMoveAction(u *Unit) *MoveAction { a.targets = newTargets(newTarget(u.Controller().gameState, moveActionTargetDesc, a)) - a.resolveFunc = func(s *State) { + a.resolveFunc = func(s *LocalState) { t := targetTile(a.Target()) movePermanent(u, t) } @@ -313,7 +340,7 @@ func (a *MoveAction) String() string { } func genBaseActionCost(u *Unit, attack, move int) ActionCostFunc { - return func(*State) bool { + return func(*LocalState) bool { if u.AvailAttackActions < attack || u.AvailMoveActions < move { return false } @@ -332,7 +359,7 @@ func genMoveActionCost(u *Unit) ActionCostFunc { } func genFullActionCost(u *Unit) ActionCostFunc { - return func(*State) bool { + return func(*LocalState) bool { if u.AvailAttackActions < 1 || u.AvailMoveActions < 1 { return false } @@ -356,7 +383,7 @@ func NewEquipAction(u *Unit, e *Equipment) *EquipAction { e, } - a.resolveFunc = func(s *State) { + a.resolveFunc = func(s *LocalState) { u.equip(e) } @@ -382,7 +409,7 @@ func NewAttackAction(u *Unit) *AttackAction { a.targets = newTargets(newTarget(u.Controller().gameState, attackActionTargetDesc, a)) - a.resolveFunc = func(s *State) { + a.resolveFunc = func(s *LocalState) { s.fight(u, a.Target().sel[0].(Permanent)) } @@ -415,7 +442,7 @@ func newArtifactSwitchAction(u *Unit, artifact Permanent) *ArtifactSwitchAction artifact, } - a.resolveFunc = func(s *State) { + a.resolveFunc = func(s *LocalState) { s.switchPermanents(a.Source().(*Unit), a.Artifact) } @@ -444,7 +471,7 @@ func newArtifactMoveAction(u *Unit, artifact Permanent) *ArtifactMoveAction { a.targets = newArtifactMoveTargets(u.Controller().gameState, a) - a.resolveFunc = func(s *State) { + a.resolveFunc = func(s *LocalState) { tile := targetTile(a.Targets().ts[0]) movePermanent(u, tile) @@ -483,7 +510,7 @@ func newFullAction(u *Unit, proto ActionFuncPrototype, desc string) *FullAction desc: desc, } - a.resolveFunc = func(s *State) { + a.resolveFunc = func(s *LocalState) { rf := proto(a) rf(s) } @@ -518,7 +545,7 @@ func NewFreeAction(p Permanent, resolveProto ActionFuncPrototype, costFunc Actio desc, } - a.resolveFunc = func(s *State) { + a.resolveFunc = func(s *LocalState) { rf := resolveProto(a) rf(s) } @@ -526,6 +553,8 @@ func NewFreeAction(p Permanent, resolveProto ActionFuncPrototype, costFunc Actio return a } +func (a *FreeAction) Speed() ActionSpeed { return fast } + func (a *FreeAction) String() string { p := a.source.(Permanent) if a.targets.RequireSelection() { @@ -543,7 +572,7 @@ func (a *BuyAction) card() *Card { return a.Target().sel[0].(*Card) } -func (a *BuyAction) CheckTargets(s *State) error { +func (a *BuyAction) CheckTargets(s *LocalState) error { err := a.targets.CheckTargets(s) if err != nil { return err @@ -551,11 +580,11 @@ func (a *BuyAction) CheckTargets(s *State) error { return s.isValidBuy(a) } -func (a *BuyAction) resolve(s *State) { +func (a *BuyAction) resolve(s *LocalState) { a.player.Store.MoveCard(a.card(), a.player.DiscardPile) } -func (a *BuyAction) PayCosts(s *State) bool { +func (a *BuyAction) PayCosts(s *LocalState) bool { cost := a.card().BuyCosts.Costs(s) if cost < 0 || cost > a.player.Resource { return false @@ -585,7 +614,7 @@ type UpkeepAction struct { TargetSelection } -func (a *UpkeepAction) PayCosts(*State) bool { +func (a *UpkeepAction) PayCosts(*LocalState) bool { costs := 0 for _, t := range a.Target().sel { u := t.(*Unit) @@ -600,7 +629,7 @@ func (a *UpkeepAction) PayCosts(*State) bool { return true } -func (a *UpkeepAction) resolve(*State) { +func (a *UpkeepAction) resolve(*LocalState) { p := a.player s := p.gameState for _, i := range a.Target().sel { @@ -609,7 +638,7 @@ func (a *UpkeepAction) resolve(*State) { } // Keep and pay for the rest - for _, u := range s.Units { + for _, u := range s.units { if u.Controller() != p { continue } diff --git a/go/game/ai.go b/go/game/ai.go index 94a9f7ec..84b5973e 100644 --- a/go/game/ai.go +++ b/go/game/ai.go @@ -12,7 +12,7 @@ import ( ) type UnitAI struct { - s *State + s *LocalState u *Unit actions chan Action target *Target @@ -50,7 +50,7 @@ func (ai *UnitAI) Execute(f func(*UnitAI)) { } } -func NewUnitAI(s *State, u *Unit) *UnitAI { +func NewUnitAI(s *LocalState, u *Unit) *UnitAI { aiDesc := u.Card().getAI() if aiDesc == "" { return nil @@ -215,13 +215,13 @@ func moveTowardsNearestEnemyUnit(ai *UnitAI) Action { return nil } - graph := ai.s.Map.generateMapGraphFor(ai.u) + graph := ai.s.Map().generateMapGraphFor(ai.u) enemyUnits := ai.s.EnemyUnits(ai.u.Controller()) if len(enemyUnits) == 0 { return nil } - paths := findPathsToUnits(graph, ai.s.Map, ai.u, enemyUnits) + paths := findPathsToUnits(graph, ai.s.Map(), ai.u, enemyUnits) nearest := 0 for i, p := range paths { if p == nil { @@ -251,7 +251,7 @@ func moveTowardsNearestEnemyUnit(ai *UnitAI) Action { } a := NewMoveAction(ai.u) - _ = a.Target().AddSelection(ai.s.Map.TileAt(target)) + _ = a.Target().AddSelection(ai.s.Map().TileAt(target)) return a } @@ -327,7 +327,7 @@ func ShyAI(ai *UnitAI) { inEnemyAttackRange := false out: for _, enemyUnit := range ai.s.EnemyUnits(ai.u.Controller()) { - for _, p := range enemyUnit.AttackablePermanents(ai.s.Map) { + for _, p := range enemyUnit.AttackablePermanents(ai.s.Map()) { if p == ai.u { inEnemyAttackRange = true break out diff --git a/go/game/areaEffect_test.go b/go/game/areaEffect_test.go index ab058f8a..5a7ab0a6 100644 --- a/go/game/areaEffect_test.go +++ b/go/game/areaEffect_test.go @@ -11,21 +11,22 @@ func TestEnteringLeaving(t *testing.T) { symbols: H: house ` - s := NewState() + s := NewLocalState() r := strings.NewReader(mapDef) - s.Map, _ = readMap(r) + m, _ := readMap(r) + s.SetMap(m) - s.AddNewPlayer("player", NewDeck()) - p := s.Players[0] + s.AddNewPlayer("player", NewDeck(), nil) + p := s.Players()[0] - t0_0 := s.Map.TileAt(Position{0, 0}) + t0_0 := s.Map().TileAt(Position{0, 0}) entered0_0, left0_0 := false, false t0_0.effects = append(t0_0.effects, newDynamicAreaEffect( func(Permanent) { entered0_0 = true }, func(Permanent) { left0_0 = true }, )) - t0_1 := s.Map.TileAt(Position{1, 0}) + t0_1 := s.Map().TileAt(Position{1, 0}) entered0_1, left0_1 := false, false t0_1.effects = append(t0_1.effects, newDynamicAreaEffect( func(Permanent) { entered0_1 = true }, diff --git a/go/game/card.go b/go/game/card.go index ee16346c..90dd8f2b 100644 --- a/go/game/card.go +++ b/go/game/card.go @@ -90,20 +90,20 @@ func (ct CardType) IsArtifact() bool { } type cardImplementation interface { - spawnTiles(*State, *Player) []*Tile + spawnTiles(*LocalState, *Player) []*Tile fullActions(*Unit) []*FullAction freeActions(Permanent) []*FreeAction playTargets() TargetDesc - stateBasedActions(*State, Permanent) + stateBasedActions(*LocalState, Permanent) additionalSpawnsFor(Permanent, CardType) []*Tile onEntering(*Tile) onLeaving(*Tile) onPlay(*PlayAction) - onETB(*State, Permanent) + onETB(*LocalState, Permanent) onPile(containing Permanent) onUnpile(containing Permanent) onDrop(dropped Permanent) @@ -115,23 +115,23 @@ type cardImplementation interface { // default behavior. type cardImplementationBase struct{} -func (*cardImplementationBase) spawnTiles(*State, *Player) []*Tile { return nil } -func (*cardImplementationBase) fullActions(*Unit) []*FullAction { return nil } -func (*cardImplementationBase) freeActions(Permanent) []*FreeAction { return nil } +func (*cardImplementationBase) spawnTiles(*LocalState, *Player) []*Tile { return nil } +func (*cardImplementationBase) fullActions(*Unit) []*FullAction { return nil } +func (*cardImplementationBase) freeActions(Permanent) []*FreeAction { return nil } func (*cardImplementationBase) playTargets() TargetDesc { return INVALID_TARGET_DESC } -func (*cardImplementationBase) stateBasedActions(*State, Permanent) {} +func (*cardImplementationBase) stateBasedActions(*LocalState, Permanent) {} func (*cardImplementationBase) additionalSpawnsFor(Permanent, CardType) []*Tile { return nil } -func (*cardImplementationBase) onEntering(*Tile) {} -func (*cardImplementationBase) onLeaving(*Tile) {} -func (*cardImplementationBase) onPlay(*PlayAction) {} -func (*cardImplementationBase) onETB(*State, Permanent) {} -func (*cardImplementationBase) onPile(Permanent) {} -func (*cardImplementationBase) onUnpile(Permanent) {} -func (*cardImplementationBase) onDrop(Permanent) {} +func (*cardImplementationBase) onEntering(*Tile) {} +func (*cardImplementationBase) onLeaving(*Tile) {} +func (*cardImplementationBase) onPlay(*PlayAction) {} +func (*cardImplementationBase) onETB(*LocalState, Permanent) {} +func (*cardImplementationBase) onPile(Permanent) {} +func (*cardImplementationBase) onUnpile(Permanent) {} +func (*cardImplementationBase) onDrop(Permanent) {} // Map of all card implementations var cardImplementations map[string]cardImplementation diff --git a/go/game/cardImplementations.go b/go/game/cardImplementations.go index 8213432b..9797af4c 100644 --- a/go/game/cardImplementations.go +++ b/go/game/cardImplementations.go @@ -65,12 +65,12 @@ type advisorImpl struct{ cardImplementationBase } func (*advisorImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) - return func(s *State) { + return func(s *LocalState) { controller := u.Controller() controller.DrawN(1) cards := controller.PromptHandCardSelection(0, 1) if cards != nil { - controller.Hand.MoveCard(cards[0], s.Exile) + controller.Hand.MoveCard(cards[0], s.Exile()) } } } @@ -83,7 +83,7 @@ func (*missionaryImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) target := a.Target().sel[0].(*Unit) - return func(s *State) { + return func(s *LocalState) { target.adjustMarks(UnitMarks.Faith, 2) if target.Marks(UnitMarks.Faith) > target.Card().BuyCosts.Costs(s) { target.controller = u.Controller() @@ -109,14 +109,14 @@ func (*greatSwordImpl) onUnpile(p Permanent) { adjustMelee(p, -2) } type misinformationImpl struct{ cardImplementationBase } func (*misinformationImpl) onPlay(a *PlayAction) { - a.GameState().Exile.AddCard(a.Card) + a.GameState().Exile().AddCard(a.Card) } type pioneerImpl struct{ cardImplementationBase } func (*pioneerImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { - return func(s *State) { + return func(s *LocalState) { target := a.Target().sel[0] if artifact, ok := target.(*Artifact); ok { s.destroyPermanent(artifact) @@ -138,7 +138,7 @@ type recruiterImpl struct{ cardImplementationBase } func (*recruiterImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) - return func(s *State) { + return func(s *LocalState) { t := a.Target().sel[0].(*Tile) s.addNewUnit(NewCard("base/recruit"), t.Position, u.Controller()) } @@ -156,7 +156,7 @@ func (*recruiterImpl) additionalSpawnsFor(p Permanent, ct CardType) []*Tile { } s := p.Controller().gameState - return TilesInRange(s.Map, p, 1) + return TilesInRange(s.Map(), p, 1) } type shieldImpl struct{ cardImplementationBase } @@ -169,7 +169,7 @@ type taxCollectorImpl struct{ cardImplementationBase } func (*taxCollectorImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) - return func(s *State) { + return func(s *LocalState) { controller := u.Controller() controller.gainResource(2) } @@ -188,7 +188,7 @@ type wormtongueImpl struct{ cardImplementationBase } func (*wormtongueImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { - return func(s *State) { + return func(s *LocalState) { t := a.Target().sel[0].(*Player) t.DiscardPile.AddCard(NewCard("base/misinformation")) } @@ -270,7 +270,7 @@ type mineImpl struct{ cardImplementationBase } func (*mineImpl) onPlay(a *PlayAction) { stolen := 0 - for _, player := range a.GameState().Players { + for _, player := range a.GameState().Players() { if a.Controller() == player || !a.Controller().IsEnemy(player) { continue } @@ -315,7 +315,7 @@ func (*selectImpl) onPlay(a *PlayAction) { p.DrawN(1) cards := p.PromptHandCardSelection(0, 1) if cards != nil { - p.Hand.MoveCard(cards[0], a.GameState().Exile) + p.Hand.MoveCard(cards[0], a.GameState().Exile()) } } @@ -350,7 +350,7 @@ func (*transmuteImpl) playTargets() TargetDesc { func (*transmuteImpl) onPlay(a *PlayAction) { storeCard := a.targets.Cur().sel[0].(*Card) a.Controller().Store.MoveCard(storeCard, a.Controller().Hand) - a.GameState().Exile.AddCard(a.Card) + a.GameState().Exile().AddCard(a.Card) } // ====== Nautics Set ====== @@ -358,7 +358,7 @@ func (*transmuteImpl) onPlay(a *PlayAction) { var fishTrapAoE areaEffect = newGrantFullActionEffect("nautics/fisher", func(a Action) ActionResolveFunc { u := a.Source().(*Unit) - return func(s *State) { + return func(s *LocalState) { controller := u.Controller() controller.gainResource(2) } @@ -370,18 +370,18 @@ type fishTrapImpl struct { } func (*fishTrapImpl) additionalSpawnsFor(Permanent, CardType) []*Tile { return nil } -func (*fishTrapImpl) stateBasedActions(*State, Permanent) {} +func (*fishTrapImpl) stateBasedActions(*LocalState, Permanent) {} func (i *fishTrapImpl) onEntering(t *Tile) { s := t.Permanent.Controller().gameState - for _, tile := range TilesInRange(s.Map, t.Permanent, 1) { + for _, tile := range TilesInRange(s.Map(), t.Permanent, 1) { tile.addEffect(i.aoe) } } func (i *fishTrapImpl) onLeaving(t *Tile) { s := t.Permanent.Controller().gameState - for _, tile := range TilesInRange(s.Map, t.Permanent, 1) { + for _, tile := range TilesInRange(s.Map(), t.Permanent, 1) { tile.removeEffect(i.aoe) } } @@ -392,7 +392,7 @@ func (*fisherImpl) fullActions(u *Unit) []*FullAction { s := u.Controller().gameState resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) - return func(s *State) { + return func(s *LocalState) { t := a.Target().sel[0].(*Tile) s.addNewArtifact(NewCard("nautics/fish_trap"), t.Position, u.Controller()) } @@ -437,9 +437,9 @@ func (*tidesChangeImpl) onPlay(a *PlayAction) { type dejaVuImpl struct{ cardImplementationBase } func (*dejaVuImpl) onPlay(a *PlayAction) { - for _, p := range a.GameState().Players { + for _, p := range a.GameState().Players() { if !p.IsKraken() { - p.Resource = a.GameState().Map.ResourceGain + p.Resource = a.GameState().Map().ResourceGain } } } @@ -460,7 +460,7 @@ type dolphinImpl struct{ cardImplementationBase } func (*dolphinImpl) fullActions(u *Unit) []*FullAction { s := u.Controller().gameState resolvePrototype := func(a Action) ActionResolveFunc { - return func(s *State) { + return func(s *LocalState) { t := a.Target().sel[0].(*Unit) // 1. +1 Attack @@ -498,8 +498,8 @@ func (*drownedSailorImpl) onDrop(sailor Permanent) { // TODO: implement hook for the containing permanent on pile to change AI type flyingDutchmenImpl struct{ cardImplementationBase } -func (*flyingDutchmenImpl) onETB(s *State, p Permanent) { - s.addTrigger(newEnemyDeathTrigger(p, func(s *State) { +func (*flyingDutchmenImpl) onETB(s *LocalState, p Permanent) { + s.addTrigger(newEnemyDeathTrigger(p, func(s *LocalState) { sailor := NewCard("kraken/drowned_sailor") if p.Tile().IsAvailableForCard(sailor) { addPermanentToPile(p, NewUnit(sailor, nil, p.Controller())) @@ -522,9 +522,9 @@ type frostPylonImpl struct { aoe frostPylonAoE } -func (*frostPylonImpl) spawnTiles(s *State, kraken *Player) (spawns []*Tile) { +func (*frostPylonImpl) spawnTiles(s *LocalState, kraken *Player) (spawns []*Tile) { for _, u := range s.EnemyUnits(kraken) { - candiates := TilesInRange(s.Map, u, 1) + candiates := TilesInRange(s.Map(), u, 1) for _, candidate := range candiates { if candidate.IsFree() { spawns = append(spawns, candidate) @@ -534,7 +534,7 @@ func (*frostPylonImpl) spawnTiles(s *State, kraken *Player) (spawns []*Tile) { return } -func (*frostPylonImpl) stateBasedActions(s *State, p Permanent) { +func (*frostPylonImpl) stateBasedActions(s *LocalState, p Permanent) { if p.Marks(PermanentMarks.Crack) >= 3 { s.destroyPermanent(p) } @@ -542,14 +542,14 @@ func (*frostPylonImpl) stateBasedActions(s *State, p Permanent) { func (i *frostPylonImpl) onEntering(t *Tile) { s := t.Permanent.Controller().gameState - for _, tile := range TilesInRange(s.Map, t.Permanent, 1) { + for _, tile := range TilesInRange(s.Map(), t.Permanent, 1) { tile.addEffect(&i.aoe) } } func (i *frostPylonImpl) onLeaving(t *Tile) { s := t.Permanent.Controller().gameState - for _, tile := range TilesInRange(s.Map, t.Permanent, 1) { + for _, tile := range TilesInRange(s.Map(), t.Permanent, 1) { tile.removeEffect(&i.aoe) } } @@ -562,7 +562,7 @@ func (*giganticHailImpl) playTargets() TargetDesc { return INVALID_TARGET_DESC } func (*giganticHailImpl) onPlay(a *PlayAction) { s := a.GameState() - emptyTiles := s.Map.FreeTiles() + emptyTiles := s.Map().FreeTiles() choices := len(emptyTiles) for i := 0; i < 3; i++ { if choices == 0 { @@ -580,16 +580,16 @@ type soldOutImpl struct{ cardImplementationBase } func (*soldOutImpl) onPlay(a *PlayAction) { s := a.GameState() - nStores := len(s.Map.Stores) + nStores := len(s.Stores()) storePositions := make([]Position, 0, nStores) - for pos := range s.Map.Stores { + for pos := range s.Map().Stores { storePositions = append(storePositions, pos) } storePos := storePositions[s.Rand.Intn(nStores-1)] - s.Map.StoreOn(storePos).MoveInto(s.Exile) + s.Map().StoreOn(storePos).MoveInto(s.Exile()) - storeTile := s.Map.TileAt(storePos) + storeTile := s.Map().TileAt(storePos) storeTile.neutralize() } @@ -605,7 +605,7 @@ func (*tentacleSlapImpl) playTargets() TargetDesc { return INVALID_TARGET_DESC } func (*tentacleSlapImpl) onPlay(a *PlayAction) { x := 0 - for _, p := range a.GameState().Players { + for _, p := range a.GameState().Players() { if p.IsKraken() { continue } @@ -627,7 +627,7 @@ func (*unholyCannonballImpl) playTargets() TargetDesc { return INVALID_TARGET_DE func (*unholyCannonballImpl) onPlay(a *PlayAction) { s := a.GameState() kraken := a.Controller() - krakenTiles := s.Map.FilterTiles(func(t *Tile) bool { return t.Raw == "the kraken" }) + krakenTiles := s.Map().FilterTiles(func(t *Tile) bool { return t.Raw == "the kraken" }) if len(krakenTiles) != 1 { log.Fatal("No kraken tile found in Map") } @@ -658,7 +658,7 @@ type illusionistImpl struct{ cardImplementationBase } func (*illusionistImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) - return func(s *State) { + return func(s *LocalState) { t := a.Target().sel[0].(*Tile) s.addNewUnit(NewCard("exp1/illusion"), t.Position, u.Controller()) } @@ -672,8 +672,8 @@ func (*illusionistImpl) fullActions(u *Unit) []*FullAction { type illusionImpl struct{ cardImplementationBase } -func (*illusionImpl) onETB(s *State, p Permanent) { - s.addTrigger(newTargetedTrigger(p, true, func(s *State) { +func (*illusionImpl) onETB(s *LocalState, p Permanent) { + s.addTrigger(newTargetedTrigger(p, true, func(s *LocalState) { s.destroyPermanent(p) }, p.Card().getEffects()[0])) } diff --git a/go/game/cardParsing.go b/go/game/cardParsing.go index 58dea594..f9069e43 100644 --- a/go/game/cardParsing.go +++ b/go/game/cardParsing.go @@ -9,26 +9,26 @@ import ( type dynamicCardImplementation struct { card *Card - spawnTilesFunc func(*State, *Player) []*Tile + spawnTilesFunc func(*LocalState, *Player) []*Tile genFullActions func(*Unit) []*FullAction genFreeActions func(Permanent) []*FreeAction targetDesc TargetDesc - _stateBasedActions func(*State, Permanent) + _stateBasedActions func(*LocalState, Permanent) _additionalSpawnsFor func(Permanent, CardType) []*Tile _onEntering func(*Tile) _onLeaving func(*Tile) _onPlay func(*PlayAction) - _onETB func(*State, Permanent) + _onETB func(*LocalState, Permanent) _onPile func(Permanent) _onUnpile func(Permanent) _onDrop func(Permanent) } -func (impl *dynamicCardImplementation) spawnTiles(s *State, p *Player) []*Tile { +func (impl *dynamicCardImplementation) spawnTiles(s *LocalState, p *Player) []*Tile { if impl.spawnTilesFunc != nil { return impl.spawnTilesFunc(s, p) } @@ -53,7 +53,7 @@ func (impl *dynamicCardImplementation) playTargets() TargetDesc { return impl.targetDesc } -func (impl *dynamicCardImplementation) stateBasedActions(s *State, p Permanent) { +func (impl *dynamicCardImplementation) stateBasedActions(s *LocalState, p Permanent) { if impl._stateBasedActions != nil { impl._stateBasedActions(s, p) } @@ -84,7 +84,7 @@ func (impl *dynamicCardImplementation) onPlay(a *PlayAction) { } } -func (impl *dynamicCardImplementation) onETB(s *State, p Permanent) { +func (impl *dynamicCardImplementation) onETB(s *LocalState, p Permanent) { if impl._onETB != nil { impl._onETB(s, p) } @@ -140,7 +140,7 @@ func (impl *dynamicCardImplementation) parseFullAction(fullAction string) { resolveProto := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) - return func(*State) { + return func(*LocalState) { u.controller.gainResource(gain) } } @@ -174,7 +174,7 @@ func parseUnitConstrain(constrain string) func(ctx interface{}, u *Unit) bool { return nil } -func parseLocation(location string) func(interface{}, *State) []*Tile { +func parseLocation(location string) func(interface{}, *LocalState) []*Tile { r := -1 var what string if strings.Contains(location, "on") { @@ -209,8 +209,8 @@ func parseLocation(location string) func(interface{}, *State) []*Tile { if strings.Contains(what, "unit") { constrainFunc := parseUnitConstrain(what) if constrainFunc != nil { - return func(ctx interface{}, s *State) []*Tile { - origins := s.Map.FilterTiles(func(t *Tile) bool { + return func(ctx interface{}, s *LocalState) []*Tile { + origins := s.Map().FilterTiles(func(t *Tile) bool { if t.Permanent != nil && t.Permanent.Card().Type == CardTypes.Unit && constrainFunc(ctx, t.Permanent.(*Unit)) { @@ -224,7 +224,7 @@ func parseLocation(location string) func(interface{}, *State) []*Tile { } else { tiles := []*Tile{} for _, origin := range origins { - tiles = append(tiles, TilesInRangeFromOrigin(s.Map, origin.Position, r)...) + tiles = append(tiles, TilesInRangeFromOrigin(s.Map(), origin.Position, r)...) } return tiles } @@ -241,7 +241,7 @@ func (impl *dynamicCardImplementation) parsePlayModification(effect string) { what, how := tokens[0], tokens[1] if what == strings.ToLower(impl.card.Name) { if locationFunc := parseLocation(how); locationFunc != nil { - impl.spawnTilesFunc = func(p *State, s *Player) []*Tile { return locationFunc(s, p) } + impl.spawnTilesFunc = func(p *LocalState, s *Player) []*Tile { return locationFunc(s, p) } } else { log.Println("Play modification other than locations are not implemented ") } diff --git a/go/game/costs.go b/go/game/costs.go index 06e394ab..ca944920 100644 --- a/go/game/costs.go +++ b/go/game/costs.go @@ -7,7 +7,7 @@ import ( ) type ResourceCosts struct { - variadicConstraintFunc func(*State) int + variadicConstraintFunc func(*LocalState) int variadicConstraint string fixed int variadicComponents int @@ -28,7 +28,7 @@ func ParseResourceCosts(costsStr string) *ResourceCosts { return &costs } -func (c *ResourceCosts) Costs(s *State, choosenVariadicCosts ...int) int { +func (c *ResourceCosts) Costs(s *LocalState, choosenVariadicCosts ...int) int { if !c.IsVariadic() { return c.fixed } diff --git a/go/game/effect.go b/go/game/effect.go index 1fc6d5b0..1fd2a630 100644 --- a/go/game/effect.go +++ b/go/game/effect.go @@ -1,26 +1,26 @@ package game type effect interface { - apply(*State) - end(*State) + apply(*LocalState) + end(*LocalState) } type dynamicEffect struct { - _apply func(*State) - _end func(*State) + _apply func(*LocalState) + _end func(*LocalState) } -func (e *dynamicEffect) apply(s *State) { +func (e *dynamicEffect) apply(s *LocalState) { e._apply(s) } -func (e *dynamicEffect) end(s *State) { +func (e *dynamicEffect) end(s *LocalState) { e._end(s) } func newAdjustmentEffect(adjust func(Permanent, int), u *Unit, delta int) effect { - apply := func(*State) { adjust(u, delta) } - end := func(*State) { adjust(u, -delta) } + apply := func(*LocalState) { adjust(u, delta) } + end := func(*LocalState) { adjust(u, -delta) } return &dynamicEffect{apply, end} } @@ -41,7 +41,7 @@ func newHealthModificationEffect(u *Unit, delta int) effect { } func newTmpEffect(p Permanent, effect string) effect { - apply := func(*State) { p.addTmpEffect(effect) } - end := func(*State) { p.removeTmpEffect(effect) } + apply := func(*LocalState) { p.addTmpEffect(effect) } + end := func(*LocalState) { p.removeTmpEffect(effect) } return &dynamicEffect{apply, end} } diff --git a/go/game/kraken.go b/go/game/kraken.go index a836cb77..fccaa6df 100644 --- a/go/game/kraken.go +++ b/go/game/kraken.go @@ -1,6 +1,7 @@ package game import ( + "image/color" "log" "strings" "sync" @@ -26,17 +27,17 @@ var KrakenDecklist = `2 kraken/angry_squid 2 kraken/tides_change! 2 kraken/unholy_cannonball` -func NewKraken(id int, s *State) *Player { - kraken := NewPlayer(id, KRAKEN_NAME, NewDeck(), s) +func NewKraken(id int, s *LocalState, color color.Color) *Player { + kraken := NewPlayer(id, KRAKEN_NAME, NewDeck(), s, color) d := NewDeckFromDeckList(KrakenDecklist) d.Shuffle(s.Rand) kraken.Deck = d kraken.DrawPerTurn = 0 - kraken_tile := s.Map.FilterTiles(func(t *Tile) bool { return t.Raw == "the kraken" }) + kraken_tile := s.Map().FilterTiles(func(t *Tile) bool { return t.Raw == "the kraken" }) if len(kraken_tile) != 1 { - log.Fatal("No kraken tile found in Map") + log.Fatal("No kraken tile found in Map()") } s.addPermanent(NewUnitFromPath("kraken/the_kraken", kraken_tile[0], kraken)) @@ -46,9 +47,9 @@ func NewKraken(id int, s *State) *Player { return kraken } -func (s *State) AddKraken() { - kraken := NewKraken(len(s.Players)+1, s) - s.Players = append(s.Players, kraken) +func (s *LocalState) AddKraken(color color.Color) { + kraken := NewKraken(len(s.Players())+1, s, color) + s.players = append(s.players, kraken) } type KrakenControl struct { @@ -70,7 +71,7 @@ func addKrakenControl(kraken *Player) { ctrl.syncGameState.Add(1) go func() { s := kraken.gameState - for len(s.Map.WinCondition(s)) == 0 { + for len(s.Map().WinCondition(s)) == 0 { ctrl.krakenTurn() } }() @@ -112,7 +113,7 @@ func (ctrl *KrakenControl) krakenTurn() { } kraken.Hand.AddCard(c) - var costFunc ActionCostFunc = func(s *State) bool { + var costFunc ActionCostFunc = func(s *LocalState) bool { kraken.Resource -= costs return true } @@ -154,7 +155,7 @@ func (ctrl *KrakenControl) RecvAction() Action { s := ctrl.kraken.gameState kraken := ctrl.kraken if !s.IsActivePlayer(kraken) || - s.ActivePhase != Phases.ActionPhase || !s.Stack.IsEmpty() { + s.activePhase != Phases.ActionPhase || !s.stack.IsEmpty() { return NewPassPriority(ctrl.kraken) } @@ -170,8 +171,8 @@ func (p *Player) IsKraken() bool { return p.Name == KRAKEN_NAME } -func (s *State) FindKraken() *Player { - for _, p := range s.Players { +func (s *LocalState) FindKraken() *Player { + for _, p := range s.Players() { if p.IsKraken() { return p } @@ -179,17 +180,17 @@ func (s *State) FindKraken() *Player { return nil } -func (s *State) KrakenSpawnTiles() []*Tile { +func (s *LocalState) KrakenSpawnTiles() []*Tile { kraken := s.FindKraken() if !kraken.Ctrl.(*KrakenControl).activeSuprise { - return s.Map.FilterTiles(func(t *Tile) bool { + return s.Map().FilterTiles(func(t *Tile) bool { return strings.Contains(t.Raw, "deep water spawn") }) } spawns := []*Tile{} for _, u := range s.EnemyUnits(kraken) { - spawns = append(spawns, TilesInRange(s.Map, u, 1)...) + spawns = append(spawns, TilesInRange(s.Map(), u, 1)...) } return spawns } diff --git a/go/game/map.go b/go/game/map.go index 378e9f2c..e0c99911 100644 --- a/go/game/map.go +++ b/go/game/map.go @@ -29,7 +29,7 @@ type MapYml struct { StartDeckList string `yaml:"start_deck_list"` } -func DummyWinCondition(*State) []*Player { +func DummyWinCondition(*LocalState) []*Player { return []*Player{} } @@ -38,7 +38,7 @@ type Map struct { symbols map[string]string ResourceGain int StartDeckList string - WinCondition func(*State) []*Player + WinCondition func(*LocalState) []*Player Stores map[Position]*Store } diff --git a/go/game/permanent.go b/go/game/permanent.go index 99d70831..47166d61 100644 --- a/go/game/permanent.go +++ b/go/game/permanent.go @@ -99,14 +99,14 @@ func FmtPermanent(p Permanent) string { return "" } -func FindPermanent(s *State, desc string) Permanent { +func FindPermanent(s *LocalState, desc string) Permanent { var pos Position _, err := fmt.Sscanf(desc[1:strings.Index(desc, ")")], POSITION_FMT, &pos.X, &pos.Y) if err != nil { log.Panicf("desc %q not a valid permanent description", desc) } - p := s.Map.TileAt(pos).Permanent + p := s.Map().TileAt(pos).Permanent return p } diff --git a/go/game/player.go b/go/game/player.go index 89423b93..b4b7bd8b 100644 --- a/go/game/player.go +++ b/go/game/player.go @@ -21,13 +21,13 @@ type Player struct { DiscardPile *DiscardPile Deck *Deck Store *Store - gameState *State + gameState *LocalState Color color.Color Ctrl PlayerControl knownStores map[Position]bool } -func NewPlayer(id int, name string, deck *Deck, gameState *State) *Player { +func NewPlayer(id int, name string, deck *Deck, gameState *LocalState, color color.Color) *Player { store := NewStore() deck.MoveInto(store) @@ -39,9 +39,10 @@ func NewPlayer(id int, name string, deck *Deck, gameState *State) *Player { DrawPerTurn: MAX_DRAW, Hand: NewHand(), DiscardPile: NewDiscardPile(), - Deck: NewDeckFromDeckList(gameState.Map.StartDeckList), + Deck: NewDeckFromDeckList(gameState.Map().StartDeckList), Store: store, gameState: gameState, + Color: color, Ctrl: nil, knownStores: make(map[Position]bool), } @@ -50,9 +51,9 @@ func NewPlayer(id int, name string, deck *Deck, gameState *State) *Player { func (p *Player) resourceGain() int { if p.Name == "The kraken" { - return p.gameState.Map.ResourceGain * (len(p.gameState.Players) - 1) + return p.gameState.Map().ResourceGain * (len(p.gameState.Players()) - 1) } else { - return p.gameState.Map.ResourceGain + return p.gameState.Map().ResourceGain } } @@ -62,7 +63,7 @@ func (p *Player) UpkeepGain() int { func (p *Player) UpkeepCost() int { cost := 0 - for _, u := range p.gameState.Units { + for _, u := range p.gameState.units { if u.Controller() != p { continue } @@ -89,7 +90,7 @@ func (p *Player) upkeep() { // Skip upkeep prompt if player does not controll any units controllsUnits := false - for _, u := range p.gameState.Units { + for _, u := range p.gameState.units { if u.Controller() == p { controllsUnits = true break @@ -106,7 +107,7 @@ func (p *Player) upkeep() { } p.gameState.declareAction(a) - p.gameState.Stack.resolve() + p.gameState.stack.resolve() } func (p *Player) actionPhase() { @@ -116,13 +117,13 @@ func (p *Player) actionPhase() { return } p.gameState.declareAction(a) - p.gameState.Stack.resolve() + p.gameState.stack.resolve() } } func (p *Player) buyPhase() { s := p.gameState - if p.Store.Size() == 0 && s.Map.HasStores() && len(p.knownStores) == 0 { + if p.Store.Size() == 0 && s.Map().HasStores() && len(p.knownStores) == 0 { return } a := promptBuy(p.Ctrl) @@ -133,7 +134,7 @@ func (p *Player) buyPhase() { if a.Targets().HasSelections() { s.declareAction(a) - s.Stack.resolve() + s.stack.resolve() } } @@ -196,7 +197,7 @@ func (p *Player) IsEnemy(other *Player) bool { } func (p *Player) AvailableStores() (stores []*Store) { - m := p.gameState.Map + m := p.gameState.Map() for pos, store := range m.Stores { t := m.TileAt(pos) if t.Permanent != nil && t.Permanent.Controller() == p { diff --git a/go/game/stack.go b/go/game/stack.go index 8087aef7..a4ba6a49 100644 --- a/go/game/stack.go +++ b/go/game/stack.go @@ -5,11 +5,11 @@ import ( ) type Stack struct { - gameState *State + gameState *LocalState Actions []Action } -func NewStack(s *State) *Stack { +func NewStack(s *LocalState) *Stack { return &Stack{s, []Action{}} } 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 { diff --git a/go/game/targets.go b/go/game/targets.go index 58f19dc1..c5acf514 100644 --- a/go/game/targets.go +++ b/go/game/targets.go @@ -22,7 +22,7 @@ type ( } Target struct { - s *State + s *LocalState desc string requirement TargetRequirement constraint TargetConstraintFunc @@ -69,7 +69,7 @@ func (t *Targets) String() string { return s[:len(s)-1] + "]" } -func newTargetWithSel(s *State, desc TargetDesc, action Action, sel []interface{}) *Target { +func newTargetWithSel(s *LocalState, desc TargetDesc, action Action, sel []interface{}) *Target { t := &Target{ s: s, desc: desc.target, @@ -81,11 +81,11 @@ func newTargetWithSel(s *State, desc TargetDesc, action Action, sel []interface{ return t } -func newTarget(s *State, desc TargetDesc, action Action) *Target { +func newTarget(s *LocalState, desc TargetDesc, action Action) *Target { return newTargetWithSel(s, desc, action, []interface{}{}) } -func newConstraintTarget(s *State, desc TargetDesc, constraint TargetConstraintFunc, action Action, +func newConstraintTarget(s *LocalState, desc TargetDesc, constraint TargetConstraintFunc, action Action, ) *Target { return &Target{ s: s, @@ -103,7 +103,7 @@ func newTargetDesc(desc string) TargetDesc { return TargetDesc{desc, "1"} } -func newPileDropTargets(s *State, a *PileDropAction) *Targets { +func newPileDropTargets(s *LocalState, a *PileDropAction) *Targets { pile := a.pile targets := newTargets() @@ -123,7 +123,7 @@ func newPileDropTargets(s *State, a *PileDropAction) *Targets { return targets } -func newArtifactMoveTargets(s *State, a *ArtifactMoveAction) *Targets { +func newArtifactMoveTargets(s *LocalState, a *ArtifactMoveAction) *Targets { targets := newTargets() targets.ts = make([]*Target, 0, 2) @@ -202,7 +202,7 @@ func (t *Target) Options() (options []interface{}) { return options } -func (t *Target) CheckSelection(s *State) error { +func (t *Target) CheckSelection(s *LocalState) error { if t.RequireSelection() { return fmt.Errorf("Number of selected targets and required ones does not match") } @@ -251,7 +251,7 @@ func (t *Targets) Cur() *Target { return t.ts[t.idx] } func (t *Targets) Next() { t.idx++ } func (t *Targets) Targets() []*Target { return t.ts } -func (t *Targets) CheckTargets(s *State) error { +func (t *Targets) CheckTargets(s *LocalState) error { for _, target := range t.ts { if err := target.CheckSelection(s); err != nil { return err @@ -318,7 +318,7 @@ func noPreviousSelectionConstraint(targets *Targets, idx int) TargetConstraintFu } } -func targetConstraint(desc string, s *State, action Action) TargetConstraintFunc { +func targetConstraint(desc string, s *LocalState, action Action) TargetConstraintFunc { if !strings.Contains(desc, " or ") { return singleTargetConstraint(desc, s, action) } @@ -332,7 +332,7 @@ func targetConstraint(desc string, s *State, action Action) TargetConstraintFunc return disjunction(constraints...) } -func singleTargetConstraint(desc string, s *State, action Action) TargetConstraintFunc { +func singleTargetConstraint(desc string, s *LocalState, action Action) TargetConstraintFunc { constraints := []TargetConstraintFunc{} if strings.Contains(desc, "permanent") { constraints = append(constraints, parsePermanentTargetConstraint(desc, s, action)...) @@ -454,7 +454,7 @@ var ( cardTargetConstraint TargetConstraintFunc = typeTargetConstraint[*Card]("*Card") ) -func parsePermanentTargetConstraint(desc string, s *State, action Action) []TargetConstraintFunc { +func parsePermanentTargetConstraint(desc string, s *LocalState, action Action) []TargetConstraintFunc { constraints := []TargetConstraintFunc{permanentTargetConstraint} if strings.Contains(desc, "enemy permanent") { @@ -477,7 +477,7 @@ func parsePermanentTargetConstraint(desc string, s *State, action Action) []Targ return constraints } -func parseUnitTargetConstraint(desc string, s *State, action Action) []TargetConstraintFunc { +func parseUnitTargetConstraint(desc string, s *LocalState, action Action) []TargetConstraintFunc { constraints := []TargetConstraintFunc{unitTargetConstraint} if strings.Contains(desc, "enemy unit") { @@ -496,7 +496,7 @@ func parseUnitTargetConstraint(desc string, s *State, action Action) []TargetCon return constraints } -func parseArtifactTargetConstraint(constraint string, s *State, action Action) []TargetConstraintFunc { +func parseArtifactTargetConstraint(constraint string, s *LocalState, action Action) []TargetConstraintFunc { constraints := []TargetConstraintFunc{artifactTargetConstraint} return constraints } @@ -544,18 +544,18 @@ func availableTileConstraint(action Action, card *Card) TargetConstraintFunc { } } -func moveConstraint(s *State, action Action, u *Unit) TargetConstraintFunc { +func moveConstraint(s *LocalState, action Action, u *Unit) TargetConstraintFunc { return func(t interface{}) (err error) { // Some actions allow also unit targets as though they were tiles tile := relaxedTileTarget(action, t) - if slices.Contains(u.MoveRangeTiles(s.Map), tile) { + if slices.Contains(u.MoveRangeTiles(s.Map()), tile) { return nil } return fmt.Errorf("tile %v is not in %v's movement range", tile, u) } } -func parseTileTargetConstraint(desc string, s *State, action Action) []TargetConstraintFunc { +func parseTileTargetConstraint(desc string, s *LocalState, action Action) []TargetConstraintFunc { constraints := []TargetConstraintFunc{} if moveAction, ok := action.(*MoveAction); ok { @@ -624,7 +624,7 @@ func parseTileTargetConstraint(desc string, s *State, action Action) []TargetCon return constraints } -func parseCardTargetConstraint(desc string, s *State, action Action) []TargetConstraintFunc { +func parseCardTargetConstraint(desc string, s *LocalState, action Action) []TargetConstraintFunc { constraints := []TargetConstraintFunc{cardTargetConstraint} player := action.Source().(*Player) @@ -634,7 +634,7 @@ func parseCardTargetConstraint(desc string, s *State, action Action) []TargetCon if strings.Contains(desc, "opponent") { originDesc = "a opponent's" origins = []*Player{} - for _, p := range s.Players { + for _, p := range s.Players() { if p.IsEnemy(player) { origins = append(origins, p) } @@ -663,7 +663,7 @@ func parseCardTargetConstraint(desc string, s *State, action Action) []TargetCon case "hand": pocs = append(pocs, p.Hand) case "store": - if p.gameState.Map.HasStores() { + if p.gameState.Map().HasStores() { for _, s := range p.AvailableStores() { pocs = append(pocs, s) } @@ -694,23 +694,23 @@ func (t *Target) candidates() []interface{} { strings.Contains(t.desc, "artifact") || strings.Contains(t.desc, "permanent") { - return utils.TypedSliceToInterfaceSlice(t.s.Permanents) + return utils.TypedSliceToInterfaceSlice(t.s.Permanents()) } else if strings.Contains(t.desc, "tile") { - return utils.TypedSliceToInterfaceSlice(t.s.Map.AllTiles()) + return utils.TypedSliceToInterfaceSlice(t.s.Map().AllTiles()) } else if strings.Contains(t.desc, "card") { cards := NewPileOfCards() switch t.desc { case "hand card": - for _, p := range t.s.Players { + for _, p := range t.s.Players() { cards.AddPoc(p.Hand) } case "store card": - if t.s.Map.HasStores() { - for _, store := range t.s.Map.Stores { + if t.s.Map().HasStores() { + for _, store := range t.s.Stores() { cards.AddPoc(store) } } else { - for _, p := range t.s.Players { + for _, p := range t.s.Players() { cards.AddPoc(p.Store) } } diff --git a/go/game/targets_test.go b/go/game/targets_test.go index fd93ee95..44bd60de 100644 --- a/go/game/targets_test.go +++ b/go/game/targets_test.go @@ -17,22 +17,23 @@ symbols: S: street W: deep water ` - s := NewState() + s := NewLocalState() r := strings.NewReader(mapDef) - s.Map, _ = readMap(r) + m, _ := readMap(r) + s.SetMap(m) - s.AddNewPlayer("player", NewDeck()) - p := s.Players[0] + s.AddNewPlayer("player", NewDeck(), nil) + p := s.Players()[0] - pioneer := NewUnit(NewCard("base/pioneer"), s.Map.TileAt(Position{1, 1}), p) + pioneer := NewUnit(NewCard("base/pioneer"), s.Map().TileAt(Position{1, 1}), p) s.addPermanent(pioneer) - sword := newEquipmentFromPath("base/sword", s.Map.TileAt(Position{0, 1}), p) + sword := newEquipmentFromPath("base/sword", s.Map().TileAt(Position{0, 1}), p) s.addPermanent(sword) fa := pioneer.FullActions[0] targets := fa.Targets() - if err := targets.AddSelection(s.Map.TileAt(Position{1, 1})); err != nil { + if err := targets.AddSelection(s.Map().TileAt(Position{1, 1})); err != nil { t.Fatalf("TileAt(1,1) not a valid target for %v", fa) } diff --git a/go/game/tile.go b/go/game/tile.go index 89d3ec3f..4bfdcf9e 100644 --- a/go/game/tile.go +++ b/go/game/tile.go @@ -90,7 +90,7 @@ var TileNames = map[string]TileType{ var farmEffect areaEffect = newGrantFullActionEffect("misc/farmer", func(a Action) ActionResolveFunc { u := a.Source().(*Unit) - return func(s *State) { + return func(s *LocalState) { controller := u.Controller() controller.gainResource(3) } diff --git a/go/game/trigger.go b/go/game/trigger.go index 4af863e5..eb0cc424 100644 --- a/go/game/trigger.go +++ b/go/game/trigger.go @@ -86,12 +86,12 @@ type Trigger interface { Source() interface{} String() string Card() *Card - trigger(*State, Event) ([]*TriggeredAction, bool) + trigger(*LocalState, Event) ([]*TriggeredAction, bool) } type triggerBase struct { source interface{} - condition func(*State, Event) (bool, bool) + condition func(*LocalState, Event) (bool, bool) resolveFunc ActionResolveFunc costFunc ActionCostFunc desc string @@ -112,7 +112,7 @@ func (t *triggerBase) Card() *Card { return nil } -func (t *triggerBase) trigger(s *State, e Event) ([]*TriggeredAction, bool) { +func (t *triggerBase) trigger(s *LocalState, e Event) ([]*TriggeredAction, bool) { triggered, remove := t.condition(s, e) if !triggered { return nil, remove @@ -123,7 +123,7 @@ func (t *triggerBase) trigger(s *State, e Event) ([]*TriggeredAction, bool) { func newTargetedTrigger(p Permanent, singleshot bool, resolveFunc ActionResolveFunc, desc string) Trigger { t := triggerBase{source: p, resolveFunc: resolveFunc, desc: desc} - t.condition = func(_ *State, event Event) (bool, bool) { + t.condition = func(_ *LocalState, event Event) (bool, bool) { c := event.eventType == EventTypes.Target && slices.Contains(event.affected, p.(interface{})) remove := singleshot && c @@ -134,7 +134,7 @@ func newTargetedTrigger(p Permanent, singleshot bool, resolveFunc ActionResolveF func newDeathTrigger(p Permanent, singleshot bool, resolveFunc ActionResolveFunc, permCond func(Permanent) bool, desc string) Trigger { t := triggerBase{source: p, resolveFunc: resolveFunc, desc: desc} - t.condition = func(_ *State, event Event) (bool, bool) { + t.condition = func(_ *LocalState, event Event) (bool, bool) { if !(event.eventType == EventTypes.Sacrifice || event.eventType == EventTypes.Destruction) { return false, false } diff --git a/go/game/unit.go b/go/game/unit.go index b71596d4..fd08b481 100644 --- a/go/game/unit.go +++ b/go/game/unit.go @@ -159,7 +159,7 @@ func (u *Unit) AvailSlowActions() (actions []Action) { actions = append(actions, NewMoveAction(u)) } - m := u.Controller().gameState.Map + m := u.Controller().gameState.Map() if u.AvailAttackActions > 0 && !INVALID_ATTACK(u.Attack) && len(u.AttackableEnemyPermanents(m)) > 0 { @@ -221,7 +221,7 @@ func (u *Unit) AvailFreeActions() (actions []Action) { func (u *Unit) CurrentlyAvailActions() (actions []Action) { s := u.Controller().gameState - if s.ActivePlayer == u.Controller() && s.ActivePhase == Phases.ActionPhase { + if s.IsActivePlayer(u.Controller()) && s.ActivePhase() == Phases.ActionPhase { actions = append(actions, u.AvailSlowActions()...) } actions = append(actions, u.AvailFreeActions()...) diff --git a/go/game/unit_test.go b/go/game/unit_test.go index deaeb5f6..fd9b95b4 100644 --- a/go/game/unit_test.go +++ b/go/game/unit_test.go @@ -17,20 +17,21 @@ symbols: S: street W: deep water ` - s := NewState() + s := NewLocalState() r := strings.NewReader(mapDef) - s.Map, _ = readMap(r) + m, _ := readMap(r) + s.SetMap(m) - s.AddNewPlayer("player", NewDeck()) - p := s.Players[0] + s.AddNewPlayer("player", NewDeck(), nil) + p := s.Players()[0] - f := NewUnit(NewCard("nautics/fisher"), s.Map.TileAt(Position{2, 2}), p) + f := NewUnit(NewCard("nautics/fisher"), s.Map().TileAt(Position{2, 2}), p) s.addPermanent(f) - a := NewUnit(NewCard("base/archer"), s.Map.TileAt(Position{1, 1}), p) + a := NewUnit(NewCard("base/archer"), s.Map().TileAt(Position{1, 1}), p) s.addPermanent(a) - sword := newEquipmentFromPath("base/sword", s.Map.TileAt(Position{0, 1}), p) + sword := newEquipmentFromPath("base/sword", s.Map().TileAt(Position{0, 1}), p) s.addPermanent(sword) movePermanent(a, f.Tile()) @@ -61,11 +62,11 @@ symbols: t.Fatalf("Piled archer is attackable") } - if len(a.AttackableTiles(s.Map)) != 0 { + if len(a.AttackableTiles(s.Map())) != 0 { t.Fatalf("Piled archer has attackable tiles") } - movePermanent(a, s.Map.TileAt(Position{1, 1})) + movePermanent(a, s.Map().TileAt(Position{1, 1})) if len(f.Pile()) != 0 { t.Fatalf("fisher's pile is not empty") } diff --git a/go/game/winCondition.go b/go/game/winCondition.go index 4db36e1d..4204ccb1 100644 --- a/go/game/winCondition.go +++ b/go/game/winCondition.go @@ -4,9 +4,9 @@ import ( "golang.org/x/exp/slices" ) -func KingGame(s *State) []*Player { +func KingGame(s *LocalState) []*Player { foundKings := map[*Player]struct{}{} - for _, u := range s.Units { + for _, u := range s.Units() { if u.card.Name != "King" { continue } @@ -14,12 +14,12 @@ func KingGame(s *State) []*Player { foundKings[u.owner] = struct{}{} } - if len(foundKings) == len(s.Players) { + if len(foundKings) == len(s.Players()) { return []*Player{} } - loosers := make([]*Player, 0, len(s.Players)) - copy(loosers, s.Players) + loosers := make([]*Player, 0, len(s.Players())) + copy(loosers, s.Players()) for p := range foundKings { i := slices.Index(loosers, p) loosers[i] = loosers[len(loosers)-1] @@ -27,7 +27,7 @@ func KingGame(s *State) []*Player { } winners := []*Player{} - for _, p := range s.Players { + for _, p := range s.Players() { if !slices.Contains(loosers, p) { winners = append(winners, p) } diff --git a/go/server/main.go b/go/server/main.go index af051b53..bd4d139a 100644 --- a/go/server/main.go +++ b/go/server/main.go @@ -6,7 +6,7 @@ import ( "muhq.space/muhqs-game/go/game" ) -var games = []game.State{} +var games = []game.LocalState{} func main() { fmt.Println("Started muhq's game server", games) diff --git a/go/ui/mapView.go b/go/ui/mapView.go index 1a91703c..8bd2e341 100644 --- a/go/ui/mapView.go +++ b/go/ui/mapView.go @@ -39,14 +39,14 @@ const ( type MapView struct { hoverPermInfo - gameState *game.State + gameState game.State mapLayer *ebiten.Image permanentsLayer *ebiten.Image tileHighlights map[game.Position][]color.Color permanentsHighlights map[game.Permanent][]color.Color } -func NewMapView(g *game.State) *MapView { +func NewMapView(g game.State) *MapView { vw := &MapView{ gameState: g, tileHighlights: make(map[game.Position][]color.Color), @@ -57,12 +57,12 @@ func NewMapView(g *game.State) *MapView { } func (mv *MapView) Height() int { - return len(mv.gameState.Map.Tiles) * TILE_HEIGHT + return len(mv.gameState.Map().Tiles) * TILE_HEIGHT } func (mv *MapView) Width() int { maxWidth := 0 - for _, row := range mv.gameState.Map.Tiles { + for _, row := range mv.gameState.Map().Tiles { if n := len(row); n > maxWidth { maxWidth = n } @@ -86,12 +86,12 @@ func rotateTileImg(x, y int, radian float64, op *ebiten.DrawImageOptions) { func (vw *MapView) handleStreet(x, y int, op *ebiten.DrawImageOptions) *ebiten.Image { var img *ebiten.Image - connections, left, right, above, below := vw.gameState.Map.FindStreetConnections(x, y) + connections, left, right, above, below := vw.gameState.Map().FindStreetConnections(x, y) if connections == 0 { // This street is not connected to another street -> // check any other non neutral tiles - connections, left, right, above, below = vw.gameState.Map.FindAnyConnections(x, y) + connections, left, right, above, below = vw.gameState.Map().FindAnyConnections(x, y) } // This street is not connected to anything. Seams odd! @@ -135,12 +135,12 @@ func (vw *MapView) handleStreet(x, y int, op *ebiten.DrawImageOptions) *ebiten.I func (vw *MapView) handleWall(x, y int, op *ebiten.DrawImageOptions) *ebiten.Image { var img *ebiten.Image - connections, left, right, above, below := vw.gameState.Map.FindFortificationConnections(x, y) + connections, left, right, above, below := vw.gameState.Map().FindFortificationConnections(x, y) if connections == 0 { // This wall is not connected to another wall -> // check any other non neutral tiles - connections, left, right, above, below = vw.gameState.Map.FindAnyConnections(x, y) + connections, left, right, above, below = vw.gameState.Map().FindAnyConnections(x, y) } // This wall is not connected to anything. Seams odd! @@ -175,7 +175,7 @@ func (vw *MapView) handleWall(x, y int, op *ebiten.DrawImageOptions) *ebiten.Ima } func (vw *MapView) handleGate(x, y int, op *ebiten.DrawImageOptions) (img *ebiten.Image) { - _, left, right, above, below := vw.gameState.Map.FindFortificationConnections(x, y) + _, left, right, above, below := vw.gameState.Map().FindFortificationConnections(x, y) if left || right { img = assets.GetTile("gate_lr") @@ -189,7 +189,7 @@ func (vw *MapView) handleGate(x, y int, op *ebiten.DrawImageOptions) (img *ebite } func (vw *MapView) handleTower(x, y int, op *ebiten.DrawImageOptions) *ebiten.Image { - connections, left, right, above, _ := vw.gameState.Map.FindFortificationConnections(x, y) + connections, left, right, above, _ := vw.gameState.Map().FindFortificationConnections(x, y) if connections == 0 { return assets.GetTile("tower") } @@ -205,7 +205,7 @@ func (vw *MapView) handleTower(x, y int, op *ebiten.DrawImageOptions) *ebiten.Im selector += "u" } // corner cases where tower is placed on the edge - if selector != "" && !strings.Contains(selector, "r") && len(vw.gameState.Map.Tiles[y])-1 == x { + if selector != "" && !strings.Contains(selector, "r") && len(vw.gameState.Map().Tiles[y])-1 == x { selector += "r" } @@ -222,8 +222,8 @@ func (vw *MapView) handleTower(x, y int, op *ebiten.DrawImageOptions) *ebiten.Im func (vw *MapView) newLayerImage() *ebiten.Image { // TODO: support non symetric maps - maxWidth := len(vw.gameState.Map.Tiles[0]) * TILE_WIDTH - maxHeight := len(vw.gameState.Map.Tiles) * TILE_HEIGHT + maxWidth := len(vw.gameState.Map().Tiles[0]) * TILE_WIDTH + maxHeight := len(vw.gameState.Map().Tiles) * TILE_HEIGHT return ebiten.NewImage(maxWidth, maxHeight) } @@ -232,10 +232,10 @@ func (vw *MapView) drawMapLayer(screen *ebiten.Image) { vw.mapLayer = vw.newLayerImage() x_px, y_px := 0.0, 0.0 - for y := 0; y < len(vw.gameState.Map.Tiles); y++ { - for x := 0; x < len(vw.gameState.Map.Tiles[y]); x++ { + for y := 0; y < len(vw.gameState.Map().Tiles); y++ { + for x := 0; x < len(vw.gameState.Map().Tiles[y]); x++ { pos := game.Position{X: x, Y: y} - tile := vw.gameState.Map.TileAt(pos) + tile := vw.gameState.Map().TileAt(pos) var tileImg *ebiten.Image op := &ebiten.DrawImageOptions{} @@ -289,7 +289,7 @@ func (vw *MapView) drawPermanentsLayer(screen *ebiten.Image) { if vw.permanentsLayer == nil { vw.permanentsLayer = vw.newLayerImage() - for i, p := range vw.gameState.Permanents { + for i, p := range vw.gameState.Permanents() { t := p.Tile() // Skip permanents with no containing tiles (e.g. piled ones) if t == nil { @@ -391,7 +391,7 @@ func (vw *MapView) FindObjectAt(screenX, screenY int) interface{} { if relativeX >= xMargin && relativeX <= TILE_WIDTH-xMargin && relativeY >= yMargin && relativeY <= TILE_HEIGHT-yMargin { - for _, p := range vw.gameState.Permanents { + for _, p := range vw.gameState.Permanents() { t := p.Tile() if t == nil { continue @@ -407,8 +407,8 @@ func (vw *MapView) FindObjectAt(screenX, screenY int) interface{} { } } - if y < len(vw.gameState.Map.Tiles) && x < len(vw.gameState.Map.Tiles[y]) { - return &vw.gameState.Map.Tiles[y][x] + if y < len(vw.gameState.Map().Tiles) && x < len(vw.gameState.Map().Tiles[y]) { + return &vw.gameState.Map().Tiles[y][x] } return nil diff --git a/go/ui/stateBar.go b/go/ui/stateBar.go index 1686b5e1..6f7aa986 100644 --- a/go/ui/stateBar.go +++ b/go/ui/stateBar.go @@ -10,24 +10,25 @@ import ( type StateBar struct { X, Y int Width, Height int - gameState *game.State - activePlayer *game.Player + gameState game.State + activePlayerId int resourceView *TextBox phaseView *TextBox } -func NewStateBar(x, y int, width, height int, s *game.State, p *game.Player) *StateBar { - sb := &StateBar{x, y, width, height, s, p, nil, nil} +func NewStateBar(x, y int, width, height int, s game.State, p *game.Player) *StateBar { + sb := &StateBar{x, y, width, height, s, p.Id, nil, nil} return sb } func (sb *StateBar) resourceLabel() string { - return fmt.Sprintf("%d/%+d", sb.activePlayer.Resource, sb.activePlayer.UpkeepGain()) + activePlayer := sb.gameState.PlayerById(sb.activePlayerId) + return fmt.Sprintf("%d/%+d", activePlayer.Resource, activePlayer.UpkeepGain()) } func (sb *StateBar) phaseLabel() string { - return fmt.Sprintf("%s: %s", sb.gameState.ActivePlayer.Name, - sb.gameState.ActivePhase.String()) + return fmt.Sprintf("%s: %s", sb.gameState.ActivePlayer().Name, + sb.gameState.ActivePhase().String()) } func (sb *StateBar) Draw(screen *ebiten.Image) { |
