diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-09-08 20:42:26 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-09-08 20:42:26 +0200 |
| commit | 0cbb13f420840d49f1a2f12f97c779b263102337 (patch) | |
| tree | 0d81d94ae92e8dc002b269f4ac69d3dbcaf7f595 | |
| parent | 492417cf3a8109a2f063dcffe4017dfda477fef3 (diff) | |
| download | muhqs-game-0cbb13f420840d49f1a2f12f97c779b263102337.tar.gz muhqs-game-0cbb13f420840d49f1a2f12f97c779b263102337.zip | |
implement spell target parsing and magic/no!
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | data/cards/magic/no!.yml | 4 | ||||
| -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 | ||||
| -rw-r--r-- | latex/cards/de/magic/no!.tex | 2 | ||||
| -rw-r--r-- | latex/cards/en/magic/no!.tex | 2 |
8 files changed, 142 insertions, 13 deletions
@@ -121,6 +121,7 @@ Things that should be done eventually: - [ ] implement log with hoverable components - [ ] support big card grids (for sealed) - [ ] implement declared action targeting + - [ ] implement action targeting - [ ] Game Logic - [ ] implement constrained X-Cost - [ ] implement HandCardSelection with condition diff --git a/data/cards/magic/no!.yml b/data/cards/magic/no!.yml index 632ce001..77bdf9f3 100644 --- a/data/cards/magic/no!.yml +++ b/data/cards/magic/no!.yml @@ -5,5 +5,5 @@ type: spell buy: 5 play: 2 effect: - en: Counter one spell - de: Neutraliziere einen Zauberspruch + en: Counter target declared spell + de: Neutraliziere einen angekündigten Zauber deiner Wahl 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) + } +} diff --git a/latex/cards/de/magic/no!.tex b/latex/cards/de/magic/no!.tex index 0f5fc486..5ad634dd 100644 --- a/latex/cards/de/magic/no!.tex +++ b/latex/cards/de/magic/no!.tex @@ -6,7 +6,7 @@ \begin{tikzpicture} \cardtypeSpell{Nein!} \cardbuycost{5} -\cardcontent{Neutraliziere einen Zauberspruch} +\cardcontent{Neutraliziere einen angekündigten Zauber deiner Wahl} \cardplaycost{2} \cardmodule{magic} \cardborder diff --git a/latex/cards/en/magic/no!.tex b/latex/cards/en/magic/no!.tex index ccb7b8ca..a456dc3c 100644 --- a/latex/cards/en/magic/no!.tex +++ b/latex/cards/en/magic/no!.tex @@ -6,7 +6,7 @@ \begin{tikzpicture} \cardtypeSpell{No!} \cardbuycost{5} -\cardcontent{Counter one spell} +\cardcontent{Counter target declared spell} \cardplaycost{2} \cardmodule{magic} \cardborder |
