diff options
Diffstat (limited to 'go')
| -rw-r--r-- | go/game/cardImplementations.go | 12 | ||||
| -rw-r--r-- | go/game/cardImplementations_test.go | 19 | ||||
| -rw-r--r-- | go/game/targets.go | 74 | ||||
| -rw-r--r-- | go/game/targets_test.go | 41 |
4 files changed, 137 insertions, 9 deletions
diff --git a/go/game/cardImplementations.go b/go/game/cardImplementations.go index 0af99570..886bf3bb 100644 --- a/go/game/cardImplementations.go +++ b/go/game/cardImplementations.go @@ -324,6 +324,17 @@ func (*moreImpl) onPlay(a *PlayAction) { a.Controller().DrawN(2) } +type noImpl struct{ cardImplementationBase } + +func (*noImpl) playTargets() TargetDesc { + return newTargetDesc("declared spell") +} + +func (*noImpl) onPlay(a *PlayAction) { + s := a.Controller().gameState + s.counterAction(a.Target().Selection()[0].(Action)) +} + type pierceImpl struct{ cardImplementationBase } func (*pierceImpl) playTargets() TargetDesc { @@ -911,6 +922,7 @@ func init() { "magic/heal!": &healImpl{}, "magic/mine!": &mineImpl{}, "magic/more!": &moreImpl{}, + "magic/no!": &noImpl{}, "magic/pierce!": &pierceImpl{}, "magic/ritual!": &ritualImpl{}, "magic/rush!": &rushImpl{}, diff --git a/go/game/cardImplementations_test.go b/go/game/cardImplementations_test.go index 8e298491..9b356c5b 100644 --- a/go/game/cardImplementations_test.go +++ b/go/game/cardImplementations_test.go @@ -496,3 +496,22 @@ func TestApproachSupremacy(t *testing.T) { 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") + } +} diff --git a/go/game/targets.go b/go/game/targets.go index 56ec9b30..92fc6104 100644 --- a/go/game/targets.go +++ b/go/game/targets.go @@ -397,10 +397,10 @@ func singleTargetConstraint(desc string, s *LocalState, action Action) TargetCon constraints = append(constraints, parseTileTargetConstraint(desc, s, action)...) } else if strings.Contains(desc, "card") { constraints = append(constraints, parseCardTargetConstraint(desc, s, action)...) - // } else if strings.Contains("action") { + // } else if strings.Contains(desc, "action") { // constraints = append(constraints, parseActionTargetConstraint(desc, s, action)...) - // } else if strings.Contains("spell") { - // constraints = append(constraints, parseSpellTargetConstraint(desc, s, action)...) + } else if strings.Contains(desc, "spell") { + constraints = append(constraints, parseSpellTargetConstraint(desc, s, action)...) } return conjunction(constraints...) @@ -512,7 +512,7 @@ func typeTargetConstraint[T any](typeDesc string) TargetConstraintFunc { } } -func cardTypeTargetConstraint(f func(CardType) bool) TargetConstraintFunc { +func permanentCardTypeConstraint(f func(CardType) bool) TargetConstraintFunc { return func(t any) (err error) { p, ok := t.(Permanent) if !ok { @@ -530,12 +530,31 @@ func cardTypeTargetConstraint(f func(CardType) bool) TargetConstraintFunc { } } +func cardTypeConstraint(cardType CardType) TargetConstraintFunc { + return func(t any) (err error) { + c, ok := t.(*Card) + if !ok { + err = fmt.Errorf("unexpected target type %T not *Card", t) + return + } + + if c.Type != cardType { + err = fmt.Errorf("unexpected card type: %v not %v", c.Type, cardType) + return + } + + return + } +} + var ( - permanentTargetConstraint TargetConstraintFunc = typeTargetConstraint[Permanent]("Permanent") - unitTargetConstraint TargetConstraintFunc = typeTargetConstraint[*Unit]("*Unit") - tileTargetConstraint TargetConstraintFunc = typeTargetConstraint[*Tile]("*Tile") - artifactTargetConstraint TargetConstraintFunc = cardTypeTargetConstraint(CardType.IsArtifact) - cardTargetConstraint TargetConstraintFunc = typeTargetConstraint[*Card]("*Card") + permanentTargetConstraint TargetConstraintFunc = typeTargetConstraint[Permanent]("Permanent") + unitTargetConstraint TargetConstraintFunc = typeTargetConstraint[*Unit]("*Unit") + tileTargetConstraint TargetConstraintFunc = typeTargetConstraint[*Tile]("*Tile") + artifactTargetConstraint TargetConstraintFunc = permanentCardTypeConstraint(CardType.IsArtifact) + cardTargetConstraint TargetConstraintFunc = typeTargetConstraint[*Card]("*Card") + playActionTargetConstraint TargetConstraintFunc = typeTargetConstraint[*PlayAction]("*PlayAction") + spellTargetConstraint TargetConstraintFunc = cardTypeConstraint(CardTypes.Spell) ) var ErrTargetHasShroud = errors.New("target has shroud") @@ -856,6 +875,43 @@ func parseCardTargetConstraint(desc string, s *LocalState, action Action) []Targ return constraints } +func playedCardTypeConstraint(cardType CardType) TargetConstraintFunc { + return func(t any) error { + pa := t.(*PlayAction) + if pa.Card.Type != cardType { + return fmt.Errorf("played cardtype %s not %s", pa.Card.Type, cardType) + } + return nil + } +} + +func parseSpellTargetConstraint(desc string, s *LocalState, action Action) []TargetConstraintFunc { + var constraints []TargetConstraintFunc + + // The target has to be a declared play action + if strings.Contains(desc, "declared") { + constraints = []TargetConstraintFunc{ + playActionTargetConstraint, + playedCardTypeConstraint(CardTypes.Spell), + func(t any) error { + pa := t.(*PlayAction) + if !utils.InterfaceSliceContains(utils.TypedSliceToInterfaceSlice(s.stack.Actions), pa) { + return fmt.Errorf("action %s not declared", pa) + } + return nil + }, + } + // The target may be any spell card + } else { + constraints = []TargetConstraintFunc{ + cardTargetConstraint, + spellTargetConstraint, + } + } + + return constraints +} + func (t *Target) candidates() []any { c := []any{} if strings.Contains(t.desc, "unit") || diff --git a/go/game/targets_test.go b/go/game/targets_test.go index 07df4b60..134eef5f 100644 --- a/go/game/targets_test.go +++ b/go/game/targets_test.go @@ -257,3 +257,44 @@ func TestUnitConstraint(t *testing.T) { t.Fatal("'units you control' does not contain own archer") } } + +func TestSpellConstraint(t *testing.T) { + s, _, p, _ := newMockState() + + spellInDiscard := NewCard("magic/more!") + p.DiscardPile.AddCard(spellInDiscard) + p.Resource = 3 + declaredSpell := NewPlayAction(p, NewCard("magic/ritual!")) + s.declareAction(declaredSpell) + unit := NewPlayAction(p, NewCard("base/archer")) + + desc := newTargetDesc("spell") + target := newTarget(s, desc, NewPassPriority(p)) + + if err := target.AddSelection(declaredSpell); err == nil { + t.Fatal("could target declared spell") + } + + if err := target.AddSelection(spellInDiscard); err != nil { + t.Fatal("failed to target spell:", err) + } + + if err := target.AddSelection(unit); err == nil { + t.Fatal("could target unit play action:", err) + } + + desc = newTargetDesc("declared spell") + target = newTarget(s, desc, NewPassPriority(p)) + + if err := target.AddSelection(declaredSpell); err != nil { + t.Fatal("failed to target spell:", err) + } + + if err := target.AddSelection(spellInDiscard); err == nil { + t.Fatal("spell in discard pile is not declared:", err) + } + + if err := target.AddSelection(unit); err == nil { + t.Fatal("could target unit play action:", err) + } +} |
