aboutsummaryrefslogtreecommitdiff
path: root/go/game/state.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/game/state.go')
-rw-r--r--go/game/state.go258
1 files changed, 210 insertions, 48 deletions
diff --git a/go/game/state.go b/go/game/state.go
index 29267c28..97283be4 100644
--- a/go/game/state.go
+++ b/go/game/state.go
@@ -1,28 +1,31 @@
package game
import (
+ "fmt"
"image/color"
"log"
"math/rand"
"strconv"
"strings"
"time"
+
+ "golang.org/x/exp/slices"
)
type State struct {
- Decks []*Deck
- Stores []*Store
- Map *Map
- Stack *Stack
- Hands []*Hand
- DiscardPiles []*DiscardPile
- Players []*Player
- ActivePlayerId int
- ActivePhase PhaseType
- Permanents []Permanent
- Units []*Unit
- Artifacts []*Artifact
- Rand *rand.Rand
+ Decks []*Deck
+ Stores []*Store
+ Map *Map
+ Stack *Stack
+ Hands []*Hand
+ DiscardPiles []*DiscardPile
+ Players []*Player
+ ActivePlayer *Player
+ ActivePhase PhaseType
+ Permanents []Permanent
+ Units []*Unit
+ Artifacts []*Artifact
+ Rand *rand.Rand
}
func NewState() *State {
@@ -32,16 +35,33 @@ func NewState() *State {
return s
}
-func (s *State) AddNewPlayer(name string, deck *Deck, store *Store, color color.Color) {
- p := NewPlayer(len(s.Players)+1, name, deck, store, s, color)
+func (s *State) 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 *State) IsActivePlayer(p *Player) bool {
+ return s.ActivePlayer == p
+}
+
func (s *State) Loop() []*Player {
winners := []*Player{}
+
+ if s.Map.HasStores() {
+ poc := NewPileOfCards()
+ for _, p := range s.Players {
+ if p.IsKraken() {
+ continue
+ }
+
+ p.Store.MoveInto(poc)
+ }
+ s.Map.distributeStoreCards(poc, s.Rand)
+ }
+
for len(winners) == 0 {
- for i, p := range s.Players {
- s.ActivePlayerId = i
+ for _, p := range s.Players {
+ s.ActivePlayer = p
p.Turn++
if p.Deck.IsEmpty() {
@@ -50,22 +70,22 @@ func (s *State) Loop() []*Player {
p.Deck.Shuffle(s.Rand)
}
- s.ActivePhase = drawPhase
+ s.ActivePhase = Phases.DrawPhase
p.Draw()
- s.ActivePhase = upkeepPhase
+ s.ActivePhase = Phases.UpkeepPhase
p.Upkeep()
- s.PhaseChange(i)
+ s.PhaseChange()
- s.ActivePhase = actionPhase
+ s.ActivePhase = Phases.ActionPhase
p.ActionPhase()
- s.PhaseChange(i)
+ s.PhaseChange()
- s.ActivePhase = buyPhase
+ s.ActivePhase = Phases.BuyPhase
p.BuyPhase()
- s.PhaseChange(i)
+ s.PhaseChange()
- s.ActivePhase = discardPhase
+ s.ActivePhase = Phases.DiscardPhase
p.DiscardPhase()
winners = s.Map.WinCondition(s)
@@ -75,38 +95,152 @@ func (s *State) Loop() []*Player {
return winners
}
+func (s *State) IsValidPlay(a *PlayAction) error {
+ if s.ActivePhase != Phases.ActionPhase {
+ log.Fatalf("Play actions can only be declared during one's actions phase")
+ }
+
+ if a.Card.IsPermanent() {
+ pos := a.Targets.(Position)
+ spawn := s.Map.TileAt(pos)
+ if !slices.Contains(s.AvailableSpawnTiles(a.Source.(*Player), a.Card), spawn) {
+ return fmt.Errorf("Spawn %v is not a valid spawn tile for %s",
+ pos.String(), a.Card.Name)
+ }
+ }
+
+ return nil
+}
+
+func (s *State) 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 := a.Targets.(*Tile)
+
+ if !slices.Contains(u.MoveRangeTiles(s.Map), tile) {
+ return fmt.Errorf("Unit %s at %s can not move to %s",
+ u.Card.Name, u.Tile.Position.String(), tile.Position.String())
+ }
+
+ if tile.Permanent != nil {
+ return fmt.Errorf("Unit %s at %s can not move to occupied %s",
+ u.Card.Name, u.Tile.Position.String(), tile.Position.String())
+ }
+
+ return nil
+}
+
+func (s *State) IsValidAttack(a *AttackAction) error {
+ if s.ActivePhase != Phases.ActionPhase {
+ return fmt.Errorf("Attack actions can only be declared during one's actions phase")
+ }
+
+ unit := a.Source.(Unit)
+ target := a.Targets.(Permanent)
+ if !target.Attackable() {
+ return fmt.Errorf("Target %s is not attackable")
+ }
+
+ if !IsPositionInRange(unit.Tile.Position, target.GetTile().Position, unit.Attack.Range) {
+ return fmt.Errorf("Move actions can only be declared during one's actions phase")
+ }
+
+ // TODO: check move
+
+ return nil
+}
+
+func (s *State) IsValidBuy(a *BuyAction) error {
+ if s.Map.HasStores() {
+ var storeTiles []*Tile
+ for pos, store := range s.Map.Stores {
+ if slices.Contains(store.cards, a.card) {
+ storeTiles = append(storeTiles, s.Map.TileAt(pos))
+ }
+ }
+
+ if storeTiles == nil {
+ return fmt.Errorf("no store contains %s", a.card.Name)
+ }
+
+ controlsUnitOnStore := false
+ for _, tile := range storeTiles {
+ controlsUnitOnStore = controlsUnitOnStore || (tile.Permanent != nil &&
+ tile.Permanent.GetCard().Type == CardTypes.Unit &&
+ tile.Permanent.GetController() == a.player)
+ }
+ if !controlsUnitOnStore {
+ return fmt.Errorf("%s' controls no unit on a store containing %s",
+ a.player.Name, a.card.Name)
+ }
+
+ } else if !slices.Contains(a.player.Store.cards, a.card) {
+ return fmt.Errorf("%s's store does not contain %s", a.player.Name, a.card.Name)
+ }
+
+ return nil
+}
+
+func (s *State) ValidateAction(a Action) error {
+ var err error = nil
+ switch a := a.(type) {
+ case *PlayAction:
+ err = s.IsValidPlay(a)
+ case *MoveAction:
+ err = s.IsValidMove(a)
+ case *AttackAction:
+ err = s.IsValidAttack(a)
+ case *BuyAction:
+ err = s.IsValidBuy(a)
+ }
+ return err
+}
+
func (s *State) PushAction(a Action) {
if !a.PayCosts(s) {
log.Fatalf("Cost for %s could not be paid", a)
}
+
s.Stack.Push(a)
}
func (s *State) DeclareAction(a Action) {
- if !s.Stack.IsEmpty() {
- log.Fatalf("Slow action can not be declared while other actions are on the stack")
+ var err error = nil
+ switch a.(type) {
+ case *BuyAction:
+ 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() {
+ err = fmt.Errorf("Slow action can not be declared while other actions are on the stack")
+ }
}
- if s.ActivePhase != actionPhase {
- log.Fatalf("Slow action can not be declared during one's actions phase")
+ if err == nil {
+ s.PushAction(a)
}
- s.PushAction(a)
+ s.broadcastNotification(NewDeclaredActionNotification(a, err))
}
-func (s *State) DeclareFastAction(a Action) {
- s.PushAction(a)
-}
-
-func (s *State) AllPassing() bool {
+func (s *State) AllPassing(skipFirst bool) bool {
nPlayers := len(s.Players)
- for i := 0; i < nPlayers; i++ {
- p := s.Players[(i+s.ActivePlayerId)%nPlayers]
+ i := 0
+ if skipFirst {
+ i++
+ }
+ for ; i < nPlayers; i++ {
+ p := s.Players[(i+s.ActivePlayer.Id-1)%nPlayers]
p.Ctrl.SendNotification(NewPriorityNotification())
a := p.Ctrl.RecvAction()
switch a.(type) {
case *PassPriorityAction:
- log.Printf("%s passed %d/%d\n", p.Name, i, nPlayers)
+ log.Printf("%s passed %d/%d\n", p.Name, i+1, nPlayers)
default:
s.DeclareAction(a)
return false
@@ -115,15 +249,15 @@ func (s *State) AllPassing() bool {
return true
}
-func (s *State) PhaseChange(activePlayerId int) {
- log.Println("Phase change")
- for !s.AllPassing() {
+func (s *State) PhaseChange() {
+ skipFirst := true
+ for !s.AllPassing(skipFirst) {
s.Stack.Resolve()
+ skipFirst = false
}
}
func (s *State) broadcastNotification(n PlayerNotification) {
- log.Printf("broadcasting %v\n", n)
for _, p := range s.Players {
p.Ctrl.SendNotification(n)
}
@@ -236,16 +370,13 @@ func (s *State) ResolvePlay(p *Player, c *Card, targets interface{}) {
log.Printf("Resolving unhandled card type: %s\n", c.Type.String())
}
- if !c.IsPermanent() {
+ if c.IsPermanent() {
+ } else {
p.DiscardPile.AddCard(c)
}
}
func (s *State) SpawnTiles(p *Player) []*Tile {
- if p.Name == "The Kraken" {
- return s.KrakenSpawnTiles()
- }
-
spawns := s.Map.FilterTiles(func(t *Tile) bool {
if t.Type != TileTypes.Spawn {
return false
@@ -257,12 +388,43 @@ func (s *State) SpawnTiles(p *Player) []*Tile {
return spawns
}
+func (s *State) WaterSpawnTiles(p *Player) []*Tile {
+ if p.IsKraken() {
+ return s.KrakenSpawnTiles()
+ }
+
+ docks := s.Map.FilterTiles(func(t *Tile) bool {
+ if t.Type != TileTypes.Docks {
+ return false
+ }
+
+ return t.Raw == "docks" || strings.HasSuffix(t.Raw, strconv.Itoa(p.Id))
+ })
+
+ log.Println(docks)
+ spawns := []*Tile{}
+ for _, dock := range docks {
+ for _, candidate := range TilesInRangeFromOrigin(s.Map, dock.Position, 1) {
+ if candidate.Water {
+ spawns = append(spawns, candidate)
+ }
+ }
+ }
+
+ return spawns
+}
+
func (s *State) AvailableSpawnTiles(p *Player, c *Card) []*Tile {
if !c.IsPermanent() {
log.Fatalf("AvailableSpawnTiles called for %s", c.Type.String())
}
- candidates := s.SpawnTiles(p)
+ var candidates []*Tile
+ if c.hasMovementEffect("swimming") {
+ candidates = s.WaterSpawnTiles(p)
+ } else {
+ candidates = s.SpawnTiles(p)
+ }
tiles := []*Tile{}
for _, candidate := range candidates {