package game import ( "strings" "testing" "muhq.space/muhqs-game/go/utils" ) func TestPioneerFullAction(t *testing.T) { mapDef := `map: |+1 HST H H FW symbols: T: tower H: house F: farm S: street W: deep water ` s := NewLocalState() m, _ := readMap(strings.NewReader(mapDef)) s.SetMap(m) player := s.AddNewPlayer("p", NewDeck()) p := s.addNewUnit(NewCard("base/pioneer"), Position{1, 1}, player) sword := s.addNewEquipment(NewCard("base/sword"), Position{0, 1}, player) palisade := s.addNewArtifact(NewCard("base/palisade"), Position{0, 0}, player) a := p.FullActions[0] opts := a.Target().Options() if len(opts) != 11 { t.Fatal("expexted 11 targets to neutralize") } if !utils.InterfaceSliceContains(opts, sword) { t.Fatal("sword is missing") } if !utils.InterfaceSliceContains(opts, palisade) { t.Fatal("palisade is missing") } _ = a.Target().AddSelection(m.TileAt(Position{0, 1})) s.ResolveAction(a) if m.TileAt(Position{0, 1}).Type != TileTypes.Neutral { t.Fatal("tile not neutralized") } a = p.FullActions[0] if len(a.Target().Selection()) != 0 { t.Fatal("full action still has target selection") } _ = a.Target().AddSelection(palisade) s.ResolveAction(a) if m.TileAt(Position{0, 0}).Permanent != nil { t.Fatal("palisade not destroyed") } } func TestPikemanRange(t *testing.T) { mapDef := `map: |+1 SSS SSS SHS symbols: S: street H: house ` s, _, player, opo := newMockState() m, _ := readMap(strings.NewReader(mapDef)) s.SetMap(m) p := s.addNewUnit(NewCard("base/pikeman"), Position{0, 0}, player) c := s.addNewUnit(NewCard("base/cavalry"), Position{0, 2}, opo) prot := s.addNewUnit(NewCard("base/cavalry_archer"), Position{1, 2}, opo) ca := s.addNewUnit(NewCard("base/cavalry_archer"), Position{2, 1}, opo) k := s.addNewUnit(NewCard("base/knight"), Position{2, 0}, opo) attackable := utils.TypedSliceToInterfaceSlice(p.AttackableEnemyPermanents()) if !utils.InterfaceSliceContains(attackable, c) { t.Fatal("cavalry not attackable") } if !utils.InterfaceSliceContains(attackable, ca) { t.Fatal("cavalry archer not attackable") } if utils.InterfaceSliceContains(attackable, k) { t.Fatal("knight is attackable") } if utils.InterfaceSliceContains(attackable, prot) { t.Fatal("cavalry is protected") } a := NewAttackAction(p) err := a.Target().AddSelection(c) if err != nil { t.Fatal("invalid target:", err) } s.ResolveAction(a) if !c.IsDestroyed() { t.Fatal("cavalry not destroyed") } a.Target().ClearSelection() err = a.Target().AddSelection(ca) if err != nil { t.Fatal("invalid target:", err) } s.ResolveAction(a) if !c.IsDestroyed() { t.Fatal("cavalry archer not destroyed") } if p.Damage() != 1 { t.Fatal("pikeman not damaged") } a.Target().ClearSelection() err = a.Target().AddSelection(k) if err == nil { t.Fatal("unexpected valid target") } } func TestMissionary(t *testing.T) { mapDef := `map: |+1 SSS SSS SHS symbols: S: street H: house ` s, _, player, opo := newMockState() m, _ := readMap(strings.NewReader(mapDef)) s.SetMap(m) mis := s.addNewUnit(NewCard("base/missionary"), Position{0, 0}, player) king := s.addNewUnit(NewCard("misc/king"), Position{0, 2}, opo) k := s.addNewUnit(NewCard("base/knight"), Position{2, 0}, opo) r := s.addNewUnit(NewCard("base/recruit"), Position{2, 1}, opo) a := mis.FullActions[0] err := a.Target().AddSelection(king) if err != nil { t.Fatal("invalid target:", err) } for range 20 { a.resolve(s) if king.controller != king.owner { t.Fatal("king not controlled by its owner") } } a.Target().ClearSelection() _ = a.Target().AddSelection(k) a.resolve(s) if k.controller != k.owner { t.Fatal("knight not controlled by its owner") } s.ResolveAction(a) if k.controller != player { t.Fatal("knight not controlled by player") } // recruit a.Target().ClearSelection() _ = a.Target().AddSelection(r) s.ResolveAction(a) if r.controller != player { t.Fatal("recruit not controlled by player") } // TODO: test return to owner } func TestMace(t *testing.T) { s, _, player, opo := newMockState() player.Ctrl.(*MockPlayerControl).actionToSend = NewPassPriority(player) opo.Ctrl.(*MockPlayerControl).actionToSend = NewPassPriority(opo) k := s.addNewUnit(NewCard("base/knight"), Position{0, 0}, player) p := s.addNewUnit(NewCard("base/pioneer"), Position{0, 1}, opo) mace := newEquipment(NewCard("equipments/mace"), k, player) s.addPermanent(mace) if len(s.triggers) == 0 { t.Fatal("mace trigger not registered") } s.fight(k, p) s.stateBasedActions() if len(s.stack.Actions) == 0 { t.Fatal("mace not triggered") } s.stack.resolve() if p.Marks(UnitStates.Paralysis) != 1 { t.Fatal("pioneer not paralysed") } s.destroyPermanent(mace) if len(s.triggers) != 0 { t.Fatal("mace trigger not removed") } } func TestArmor(t *testing.T) { s, _, player, opo := newMockState() a := s.addNewUnit(NewCard("base/archer"), Position{0, 0}, player) p := s.addNewUnit(NewCard("base/pioneer"), Position{0, 1}, opo) armor := newEquipment(NewCard("equipments/armor"), p, opo) s.addPermanent(armor) if armor, err := p.XEffect("armor"); armor != 1 { t.Fatal("pioneer not armored", armor, err) } s.fight(a, p) if p.Damage() != 0 { t.Fatal("pioneer received damage") } } func TestShield(t *testing.T) { s, _, player, _ := newMockState() a := s.addNewUnit(NewCard("base/archer"), Position{0, 0}, player) oldHealth := a.Health shield := newEquipment(NewCard("base/shield"), a, player) s.addPermanent(shield) if a.Health == oldHealth { t.Fatal("archer healh no increased") } } func TestBanner(t *testing.T) { s, _, player, opo := newMockState() s.addNewArtifact(NewCard("equipments/banner"), Position{0, 0}, player) w := s.addNewUnit(NewCard("base/wormtongue"), Position{1, 0}, player) a := s.addNewUnit(NewCard("base/archer"), Position{0, 1}, opo) if a.Attack.DamageInRange(1) != 2 { t.Fatal("archer not buffed by banner") } if a.Movement.Range != 3 { t.Fatal("archer not sped up by banner") } if w.Attack.DamageInRange(1) != 1 { t.Fatal("wormtongue not buffed by banner") } if w.Movement.Range != 3 { t.Fatal("wormtongue not sped up by banner") } } func TestPatrician(t *testing.T) { s, _, player, _ := newMockState() p := s.addNewUnit(NewCard("exp1/patrician"), Position{1, 0}, player) fas := p.FreeActions if len(fas) != 5 { t.Fatal("patrician does not have 5 free actions") } player.Resource = 5 s.declareAction(fas[0]) if player.Resource != 3 { t.Fatal("patrician move did not cost 2 resource") } s.stack.pop() if p.Movement.Range != 2 { t.Fatal("patrician movement is not increased") } player.Resource = 5 s.declareAction(fas[1]) if player.Resource != 2 { t.Fatal("patrician attack did not cost 3 resource") } s.stack.pop() if p.Attack.DamageInRange(1) != 1 { t.Fatal("patrician has no melee") } player.Resource = 5 s.declareAction(fas[2]) if player.Resource != 0 { t.Fatal("patrician armor did not cost 5 resource") } s.stack.pop() x, err := p.XEffect("armor") if err != nil || x != 1 { t.Fatal("patrician has no armor") } } func TestRecycle(t *testing.T) { s, _, player, _ := newMockState() player.Resource = 10 rts := NewPileOfCards() rts.FromString("[magic/ritual!, magic/ritual!]") player.DiscardPile.AddPoc(rts) r := NewCard("exp1/recycle!") player.Hand.AddCard(r) a := NewPlayAction(player, r) a.Target().AddSelection(rts.Cards()[0]) a.Target().AddSelection(rts.Cards()[1]) s.declareAction(a) s.stack.pop() if player.Hand.Size() != 2 { t.Fatal("Hand does not contain two cards") } player.Hand.MoveInto(player.DiscardPile) player.DiscardPile.MoveCard(r, player.DiscardPile) a.Target().AddSelection(rts.Cards()[0]) s.declareAction(a) s.stack.pop() if player.Hand.Size() != 1 { t.Fatal("Hand does not contain one cards") } } func TestPowerToThePeople(t *testing.T) { s, _, player, _ := newMockState() player.Resource = 3 for y := range 2 { for x := range 2 { s.addNewUnit(NewCard("base/archer"), Position{x, y}, player) } } c := NewCard("exp1/power_to_the_people!") player.Hand.AddCard(c) a := NewPlayAction(player, c) s.declareAction(a) s.stack.pop() if player.Resource != 4 { t.Fatal("Not gained 4 resource") } } func TestReformer(t *testing.T) { s, _, player, _ := newMockState() r := s.addNewUnit(NewCard("exp1/reformer"), Position{0, 0}, player) c1 := NewCard("base/sword") player.Hand.AddCard(c1) ra := r.FullActions[0] s.ResolveAction(ra) s.handleTriggers() if len(s.triggers) != 1 { t.Fatal("No active reformer trigger") } player.discardCard(c1) if len(s.events) != 1 { t.Fatal("No discard event fired") } s.handleTriggers() if s.stack.IsEmpty() { t.Fatal("reformer draw not triggered") } if len(s.triggers) != 1 { t.Fatal("Reformer trigger removed") } s.stack.pop() if player.Hand.IsEmpty() { t.Fatal("no card drawn") } player.DiscardPile.MoveCard(c1, player.Hand) da := NewFreeDiscardAction(player) da.Target().AddSelection(player.Hand.Cards()[0]) da.Target().AddSelection(player.Hand.Cards()[1]) s.declareAction(da) s.stack.pop() s.handleTriggers() s.stack.pop() if player.Hand.Size() != 2 { t.Fatal("not two cards drawn") } } func TestSailor(t *testing.T) { mapDef := `map: |+1 HW symbols: H: house W: deep water ` s, m, p, _ := newMockStateFromDef(mapDef) sailor := s.addNewUnit(NewCard("nautics/sailor"), Position{0, 0}, p) fisher := s.addNewUnit(NewCard("nautics/fisher"), Position{1, 0}, p) _, err := sailor.XEffect("crew") if err == nil { t.Fatal("sailor is crewable") } ma := NewMoveAction(sailor) ma.Target().AddSelection(m.TileAt(Position{1, 0})) s.ResolveAction(ma) if fisher.Movement.Range != 3 { t.Fatal("sailor not adding movement to fisher") } } func TestPierce(t *testing.T) { s, _, p, _ := newMockState() knight := s.addNewUnit(NewCard("base/knight"), Position{0, 0}, p) archer := s.addNewUnit(NewCard("base/archer"), Position{1, 0}, p) pa := NewPlayAction(p, NewCard("magic/pierce!")) pa.Target().AddSelection(knight) s.ResolveAction(pa) x, err := knight.XEffect("armor") if err != nil { t.Fatal("unexpected", err) } if x != 0 { t.Fatal("expected 0 armor") } pa.Target().AddSelection(archer) s.ResolveAction(pa) x, err = archer.XEffect("armor") if err != nil { t.Fatal("unexpected", err) } if x != -1 { t.Fatal("expected -1 armor") } s.fight(knight, archer) if knight.Damage() != 1 { t.Fatal("Knight did not take 1 damage") } if archer.Damage() != 2 { t.Fatal("Archer did not take 2 damage") } } func TestApproachSupremacy(t *testing.T) { s, _, p, _ := newMockState() p.Resource = 60 pa := NewPlayAction(p, NewCard("exp1/approach_supremacy!")) // Play the first time s.declareAction(pa) s.stack.pop() // resolve once if p.Won { t.Fatal("player already won") } // Play the second time s.declareAction(pa) s.counterAction(pa) // do not resolve it if p.Won { t.Fatal("player already won") } // Play the third time s.declareAction(pa) s.stack.pop() // resolve it if !p.Won { t.Fatal("player did not win") } } func TestNo(t *testing.T) { s, _, p, o := newMockState() p.Resource = 20 o.Resource = 6 approach := NewPlayAction(p, NewCard("exp1/approach_supremacy!")) no := NewPlayAction(o, NewCard("magic/no!")) s.declareAction(approach) no.Target().AddSelection(approach) s.declareAction(no) s.stack.pop() // Resolve no! if !s.stack.IsEmpty() { t.Fatal("Approach not countered") } } func TestRelic(t *testing.T) { s, _, p, o := newMockState() p.Resource = 0 o.Resource = 0 s.addNewArtifact(NewCard("equipments/relic"), Position{0, 0}, p) s.changePhase(upkeepPhase) if !s.stack.IsEmpty() { t.Fatal("Unexpected relic trigger") } s.addNewUnit(NewCard("base/archer"), Position{0, 1}, p) s.changePhase(upkeepPhase) if s.stack.IsEmpty() { t.Fatal("Relic not triggered") } s.stack.pop() if p.Resource != 1 { t.Fatal("player did not gain 1 resource") } s.activePlayerId = o.Id s.changePhase(upkeepPhase) if !s.stack.IsEmpty() { t.Fatal("Relic triggered in opponents upkeep") } s.activePlayerId = p.Id s.addNewUnit(NewCard("base/archer"), Position{1, 1}, o) s.changePhase(upkeepPhase) if s.stack.IsEmpty() { t.Fatal("Relic not triggered") } s.stack.pop() if p.Resource != 2 && o.Resource != 1 { t.Fatal("player and opponent did not gain 1 resource each") } s.addNewUnit(NewCard("base/archer"), Position{1, 0}, o) s.changePhase(upkeepPhase) if s.stack.IsEmpty() { t.Fatal("Relic not triggered") } s.stack.pop() if p.Resource != 2 { t.Fatal("player resource changed") } if o.Resource != 2 { t.Fatal("opponent did not gain 1 resource") } }