diff options
| -rw-r--r-- | go/TODO | 1 | ||||
| -rw-r--r-- | go/game/action.go | 15 | ||||
| -rw-r--r-- | go/game/areaEffect_test.go | 6 | ||||
| -rw-r--r-- | go/game/cardImplementations.go | 6 | ||||
| -rw-r--r-- | go/game/equipment.go | 12 | ||||
| -rw-r--r-- | go/game/kraken.go | 2 | ||||
| -rw-r--r-- | go/game/state.go | 84 | ||||
| -rw-r--r-- | go/game/targets_test.go | 4 | ||||
| -rw-r--r-- | go/game/unit_test.go | 6 |
9 files changed, 82 insertions, 54 deletions
@@ -1,6 +1,5 @@ * Fix data race between UI and game loop (possible big state lock) * implement representation of next target and current seelction in prompt -* implement equipment play equipped to next unit * implement equipment actions * drop * implement Pile UI hint diff --git a/go/game/action.go b/go/game/action.go index 271f5f51..fbabb06b 100644 --- a/go/game/action.go +++ b/go/game/action.go @@ -127,7 +127,10 @@ type PlayAction struct { ActionBase } -var permanentPlayActionTarget = TargetDesc{"available spawn tile", "1"} +var ( + permanentPlayActionTarget = TargetDesc{"available spawn tile", "1"} + equipmentPlayActionTarget = TargetDesc{"available spawn tile", "?"} +) func NewPlayAction(p *Player, c *Card, args ...interface{}) *PlayAction { a := &PlayAction{ActionBase{ @@ -155,7 +158,11 @@ func NewPlayAction(p *Player, c *Card, args ...interface{}) *PlayAction { s := p.gameState if c.IsPermanent() { - a.targets = newTargets(newTarget(s, permanentPlayActionTarget, a)) + if c.Type == CardTypes.Equipment { + a.targets = newTargets(newTarget(s, equipmentPlayActionTarget, a)) + } else { + a.targets = newTargets(newTarget(s, permanentPlayActionTarget, a)) + } } else if targetDesc := c.Impl.playTargets(); targetDesc != INVALID_TARGET_DESC { a.targets = newTargets(newTarget(s, targetDesc, a)) } else { @@ -175,6 +182,10 @@ func (a *PlayAction) String() string { return fmt.Sprintf(" %s play %s", p.Name, a.Card.Name) } + if a.Card.Type == CardTypes.Equipment && len(a.Target().sel) == 0 { + return fmt.Sprintf("%s play %s@next unit", p.Name, a.Card.Name) + } + return fmt.Sprintf("%s play %s@%v", p.Name, a.Card.Name, a.targets) } diff --git a/go/game/areaEffect_test.go b/go/game/areaEffect_test.go index 8adca76a..ab058f8a 100644 --- a/go/game/areaEffect_test.go +++ b/go/game/areaEffect_test.go @@ -33,7 +33,7 @@ symbols: )) palisade := NewUnit(NewCard("base/archer"), t0_0, p) - s.AddPermanent(palisade) + s.addPermanent(palisade) if !entered0_0 || palisade.Tile() != t0_0 || t0_0.Permanent != palisade { t.Fatalf("palisade did not enter tile %v", t0_0) } @@ -45,7 +45,7 @@ symbols: entered0_0, left0_0 = false, false archer := NewUnit(NewCard("base/archer"), t0_0, p) - s.AddPermanent(archer) + s.addPermanent(archer) if !entered0_0 || archer.Tile() != t0_0 || t0_0.Permanent != archer { t.Fatalf("archer did not enter tile %v", t0_0) } @@ -65,5 +65,5 @@ symbols: } sword := newEquipmentFromPath("base/sword", t, p) - s.AddPermanent(sword) + s.addPermanent(sword) } diff --git a/go/game/cardImplementations.go b/go/game/cardImplementations.go index e33906f6..b015ace8 100644 --- a/go/game/cardImplementations.go +++ b/go/game/cardImplementations.go @@ -140,7 +140,7 @@ func (*recruiterImpl) fullActions(u *Unit) []*FullAction { u := a.Source().(*Unit) return func(s *State) { t := a.Target().sel[0].(*Tile) - s.AddNewUnit(NewCard("base/recruit"), t.Position, u.Controller()) + s.addNewUnit(NewCard("base/recruit"), t.Position, u.Controller()) } } @@ -370,7 +370,7 @@ func (*fisherImpl) fullActions(u *Unit) []*FullAction { u := a.Source().(*Unit) return func(s *State) { t := a.Target().sel[0].(*Tile) - s.AddNewArtifact(NewCard("nautics/fish_trap"), t.Position, u.Controller()) + s.addNewArtifact(NewCard("nautics/fish_trap"), t.Position, u.Controller()) } } a := newFullAction(u, resolvePrototype, "create fish trap") @@ -537,7 +537,7 @@ func (*giganticHailImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { } choice := s.Rand.Intn(choices - 1) - s.AddNewArtifact(NewCard("kraken/ice_berg"), emptyTiles[choice].Position, kraken) + s.addNewArtifact(NewCard("kraken/ice_berg"), emptyTiles[choice].Position, kraken) emptyTiles[choice] = emptyTiles[choices-1] choices-- } diff --git a/go/game/equipment.go b/go/game/equipment.go index 9e9860e8..32bb358c 100644 --- a/go/game/equipment.go +++ b/go/game/equipment.go @@ -16,11 +16,13 @@ func newEquipment(card *Card, containing interface{}, owner *Player) *Equipment card.Values["durability"].(int), } - switch containing := containing.(type) { - case *Tile: - e.tile = containing - case *Unit: - addPermanentToPile(containing, e) + if containing != nil { + switch containing := containing.(type) { + case *Tile: + e.tile = containing + case *Unit: + addPermanentToPile(containing, e) + } } return e diff --git a/go/game/kraken.go b/go/game/kraken.go index 8c82fc9d..e5db4649 100644 --- a/go/game/kraken.go +++ b/go/game/kraken.go @@ -39,7 +39,7 @@ func NewKraken(id int, s *State) *Player { log.Fatal("No kraken tile found in Map") } - s.AddPermanent(NewUnitFromPath("kraken/the_kraken", kraken_tile[0], kraken)) + s.addPermanent(NewUnitFromPath("kraken/the_kraken", kraken_tile[0], kraken)) addKrakenControl(kraken) diff --git a/go/game/state.go b/go/game/state.go index 604e4530..87e1ee3d 100644 --- a/go/game/state.go +++ b/go/game/state.go @@ -12,20 +12,21 @@ import ( ) type State struct { - Decks []*Deck - Stores []*Store - Exile *PileOfCardsBase - Map *Map - Stack *Stack - Hands []*Hand - DiscardPiles []*DiscardPile - Players []*Player - ActivePlayer *Player - ActivePhase PhaseType - Permanents []Permanent - Units []*Unit - Rand *rand.Rand - EotEffects []effect + Decks []*Deck + Stores []*Store + Exile *PileOfCardsBase + Map *Map + Stack *Stack + Hands []*Hand + DiscardPiles []*DiscardPile + Players []*Player + ActivePlayer *Player + ActivePhase PhaseType + Permanents []Permanent + Units []*Unit + Rand *rand.Rand + EotEffects []effect + outstandingEquipment []*Card } func NewState() *State { @@ -114,6 +115,11 @@ func (s *State) IsValidPlay(a *PlayAction) error { } if a.Card.IsPermanent() { + // Equipments may be played not on a spawn tile but equiped to the next played unit + if a.Card.Type == CardTypes.Equipment && len(a.Target().sel) == 0 { + return nil + } + spawn := a.Target().sel[0].(*Tile) if !slices.Contains(s.AvailableSpawnTiles(a.Source().(*Player), a.Card), spawn) { return fmt.Errorf("Spawn %v is not a valid spawn tile for %s", @@ -354,7 +360,7 @@ func (s *State) broadcastNotification(n PlayerNotification) { } } -func (s *State) AddPermanent(perm Permanent) { +func (s *State) addPermanent(perm Permanent) { s.Permanents = append(s.Permanents, perm) switch p := perm.(type) { case *Unit: @@ -366,26 +372,22 @@ func (s *State) AddPermanent(perm Permanent) { } } -func (s *State) AddNewUnit(card *Card, pos Position, owner *Player) { - s.AddPermanent(NewUnit(card, s.Map.TileAt(pos), owner)) -} - -func (s *State) AddNewArtifact(card *Card, pos Position, owner *Player) { - s.AddPermanent(NewArtifact(card, s.Map.TileAt(pos), owner)) +func (s *State) addNewUnit(card *Card, pos Position, owner *Player) *Unit { + u := NewUnit(card, s.Map.TileAt(pos), owner) + s.addPermanent(u) + return u } -func (s *State) addNewEquipment(card *Card, pos Position, owner *Player) { - s.AddPermanent(newEquipment(card, s.Map.TileAt(pos), owner)) +func (s *State) addNewArtifact(card *Card, pos Position, owner *Player) *Artifact { + a := NewArtifact(card, s.Map.TileAt(pos), owner) + s.addPermanent(a) + return a } -func (s *State) AddNewPermFromPath(cardPath string, pos Position, owner *Player) { - card := NewCard(cardPath) - switch card.Type { - case CardTypes.Unit: - s.AddNewUnit(card, pos, owner) - case CardTypes.Artifact: - s.AddNewArtifact(card, pos, owner) - } +func (s *State) 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) { @@ -470,12 +472,21 @@ func (s *State) resolvePlay(p *Player, c *Card, targets *Targets) { switch c.Type { case CardTypes.Unit: tile := targets.ts[0].sel[0].(*Tile) - s.AddNewUnit(c, tile.Position, p) + u := s.addNewUnit(c, tile.Position, p) + if len(s.outstandingEquipment) > 0 { + for _, e := range s.outstandingEquipment { + u.equip(newEquipment(e, nil, p)) + } + s.outstandingEquipment = nil + } case CardTypes.Artifact: tile := targets.ts[0].sel[0].(*Tile) - s.AddNewArtifact(c, tile.Position, p) + s.addNewArtifact(c, tile.Position, p) case CardTypes.Equipment: - if tile, ok := targets.ts[0].sel[0].(*Tile); ok { + if len(targets.ts[0].sel) == 0 { + s.outstandingEquipment = append(s.outstandingEquipment, c) + } else { + tile, _ := targets.ts[0].sel[0].(*Tile) s.addNewEquipment(c, tile.Position, p) } case CardTypes.Spell: @@ -584,6 +595,11 @@ func (s *State) addEotEffect(e effect) { } func (s *State) endOfTurn() { + // Move all outstanding equipment cards to the discard pile + for _, e := range s.outstandingEquipment { + s.ActivePlayer.DiscardPile.AddCard(e) + } + for _, e := range s.EotEffects { e.end(s) } diff --git a/go/game/targets_test.go b/go/game/targets_test.go index be1cdad9..fd93ee95 100644 --- a/go/game/targets_test.go +++ b/go/game/targets_test.go @@ -25,10 +25,10 @@ symbols: p := s.Players[0] pioneer := NewUnit(NewCard("base/pioneer"), s.Map.TileAt(Position{1, 1}), p) - s.AddPermanent(pioneer) + s.addPermanent(pioneer) sword := newEquipmentFromPath("base/sword", s.Map.TileAt(Position{0, 1}), p) - s.AddPermanent(sword) + s.addPermanent(sword) fa := pioneer.FullActions[0] targets := fa.Targets() diff --git a/go/game/unit_test.go b/go/game/unit_test.go index 4cfcdfb9..deaeb5f6 100644 --- a/go/game/unit_test.go +++ b/go/game/unit_test.go @@ -25,13 +25,13 @@ symbols: p := s.Players[0] f := NewUnit(NewCard("nautics/fisher"), s.Map.TileAt(Position{2, 2}), p) - s.AddPermanent(f) + s.addPermanent(f) a := NewUnit(NewCard("base/archer"), s.Map.TileAt(Position{1, 1}), p) - s.AddPermanent(a) + s.addPermanent(a) sword := newEquipmentFromPath("base/sword", s.Map.TileAt(Position{0, 1}), p) - s.AddPermanent(sword) + s.addPermanent(sword) movePermanent(a, f.Tile()) if len(f.Pile()) != 1 { |
