diff options
Diffstat (limited to 'go/game/state.go')
| -rw-r--r-- | go/game/state.go | 258 |
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 { |
