aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2024-12-27 12:40:19 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-01-27 16:43:58 +0100
commit4e2ab55d345fd5e5c5d2d5bc56948611b15564d5 (patch)
treec6998ac54687c442ad0baa541edd0fbf917b1308
parent5aa8e7ddbc41aed55e7918e6afaec0b5c07fa510 (diff)
downloadmuhqs-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.go47
-rw-r--r--go/client/startMenu.go12
-rw-r--r--go/game/action.go149
-rw-r--r--go/game/ai.go12
-rw-r--r--go/game/areaEffect_test.go13
-rw-r--r--go/game/card.go28
-rw-r--r--go/game/cardImplementations.go74
-rw-r--r--go/game/cardParsing.go24
-rw-r--r--go/game/costs.go4
-rw-r--r--go/game/effect.go20
-rw-r--r--go/game/kraken.go31
-rw-r--r--go/game/map.go4
-rw-r--r--go/game/permanent.go4
-rw-r--r--go/game/player.go25
-rw-r--r--go/game/stack.go4
-rw-r--r--go/game/state.go296
-rw-r--r--go/game/targets.go50
-rw-r--r--go/game/targets_test.go15
-rw-r--r--go/game/tile.go2
-rw-r--r--go/game/trigger.go10
-rw-r--r--go/game/unit.go4
-rw-r--r--go/game/unit_test.go19
-rw-r--r--go/game/winCondition.go12
-rw-r--r--go/server/main.go2
-rw-r--r--go/ui/mapView.go40
-rw-r--r--go/ui/stateBar.go15
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) {