diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-07-21 08:27:45 -0400 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-07-22 12:54:28 -0400 |
| commit | f6d557d1ba93c7b578733a2d2da6c92f5e3c649c (patch) | |
| tree | ccc029224388ec2443b46a44170fcc7052982dba | |
| parent | 28a858ce697af5f23c3e2ce8b200d4ae4db1a90a (diff) | |
| download | muhqs-game-f6d557d1ba93c7b578733a2d2da6c92f5e3c649c.tar.gz muhqs-game-f6d557d1ba93c7b578733a2d2da6c92f5e3c649c.zip | |
implement "you control" target constraint
| -rw-r--r-- | go/game/action.go | 6 | ||||
| -rw-r--r-- | go/game/action_test.go | 42 | ||||
| -rw-r--r-- | go/game/targets.go | 29 | ||||
| -rw-r--r-- | go/utils/slices.go | 18 |
4 files changed, 87 insertions, 8 deletions
diff --git a/go/game/action.go b/go/game/action.go index 44fdd97e..f29b8b4c 100644 --- a/go/game/action.go +++ b/go/game/action.go @@ -124,14 +124,14 @@ func newRandomDraftPick(p *Player, n PlayerNotification) Action { } type ActionBase struct { - source interface{} + source any Card *Card targets *Targets resolveFunc ActionResolveFunc costFunc ActionCostFunc } -func (a *ActionBase) Source() interface{} { return a.source } +func (a *ActionBase) Source() any { return a.source } func (a *ActionBase) Controller() *Player { switch t := a.source.(type) { case *Player: @@ -648,7 +648,7 @@ func newBuyAction(p *Player) *BuyAction { return a } -var upkeepActionTargetDesc = TargetDesc{"unit you controll", "*"} +var upkeepActionTargetDesc = TargetDesc{"unit you control", "*"} type UpkeepAction struct { TargetSelection diff --git a/go/game/action_test.go b/go/game/action_test.go index 1c5a7262..a4cf0752 100644 --- a/go/game/action_test.go +++ b/go/game/action_test.go @@ -2,6 +2,8 @@ package game import ( "testing" + + "muhq.space/muhqs-game/go/utils" ) const PASS_STRING = "muhq\u00A0pass" @@ -9,6 +11,46 @@ const PASS_STRING = "muhq\u00A0pass" var s *LocalState = NewLocalState() var p *Player = s.AddNewPlayer("muhq", NewDeck()) +func TestUpkeepAction(t *testing.T) { + s := NewLocalState() + p := s.AddNewPlayer("muhq", NewDeck()) + o := s.AddNewPlayer("opponent", NewDeck()) + m, _ := GetMap("2P-ring-street") + s.SetMap(m) + + archer := s.addNewUnit(NewCard("base/archer"), Position{0, 0}, p) + farmer := s.addNewUnit(NewCard("misc/farmer"), Position{0, 1}, p) + // Change controller + knight := s.addNewUnit(NewCard("base/knight"), Position{0, 2}, o) + knight.controller = p + + king := s.addNewUnit(NewCard("misc/king"), Position{2, 2}, o) + + n := newUpkeepPrompt(p) + ctx := n.Context.(TargetSelectionCtx) + if ctx.Action.Source().(*Player) != p { + t.Fatal("unexpected player ", p) + } + + trgts := ctx.Action.Targets() + if !trgts.AllowSelection() { + t.Fatal("upkeep action does not allow target selection") + } + + if trgts.RequireSelection() { + t.Fatal("upkeep action does require selection") + } + + opts := trgts.Options() + if !utils.InterfaceSliceContains(opts, archer) || !utils.InterfaceSliceContains(opts, farmer) || !utils.InterfaceSliceContains(opts, farmer) { + t.Fatal("Upkeep action targets does not contain the controlled units") + } + + if utils.InterfaceSliceContains(opts, king) { + t.Fatal("Upkeep action targets contains uncontrolled units") + } +} + func TestMarshalPass(t *testing.T) { exp := PASS_STRING a := NewPassPriority(p) diff --git a/go/game/targets.go b/go/game/targets.go index 2e29d64e..1be206e7 100644 --- a/go/game/targets.go +++ b/go/game/targets.go @@ -190,7 +190,7 @@ var ( // AddSelection adds an object as selection to the target. // If the provided object does not satisfy the target contraint err != nil is returned. -// Selecting the same object multiple times reaul%s in an error. +// Selecting the same object multiple times results in an error. func (t *Target) AddSelection(sel any) (err error) { if slices.Contains(t.sel, sel) { return ErrAlreadySelected @@ -393,6 +393,27 @@ func enemyPermanentTargetConstraint(action Action) TargetConstraintFunc { } } +func controlledPermanentTargetConstraint(action Action) TargetConstraintFunc { + var sourceController *Player + switch source := action.Source().(type) { + case *Player: + sourceController = source + case Permanent: + sourceController = source.Controller() + default: + log.Panicf("Unhandled source type %T in enemyPermanentTargetConstraint", source) + } + + return func(t any) (err error) { + p, _ := t.(Permanent) + if p.Controller() != sourceController { + err = fmt.Errorf("Controller %v of target %v is %v", + p.Controller(), t, sourceController) + } + return + } +} + func attackableTargetConstraint(action Action) TargetConstraintFunc { return func(t interface{}) (err error) { p, _ := t.(Permanent) @@ -496,13 +517,17 @@ func parsePermanentTargetConstraint(desc string, s *LocalState, action Action) [ return constraints } -func parseUnitTargetConstraint(desc string, s *LocalState, action Action) []TargetConstraintFunc { +func parseUnitTargetConstraint(desc string, _ *LocalState, action Action) []TargetConstraintFunc { constraints := []TargetConstraintFunc{unitTargetConstraint} if strings.Contains(desc, "enemy unit") { constraints = append(constraints, enemyPermanentTargetConstraint(action)) } + if strings.Contains(desc, "you control") { + constraints = append(constraints, controlledPermanentTargetConstraint(action)) + } + if strings.Contains(desc, " in range ") { tokens := strings.SplitN(desc, " in range ", 2) r, err := strconv.Atoi(tokens[1]) diff --git a/go/utils/slices.go b/go/utils/slices.go index eb02f782..d12b4ee0 100644 --- a/go/utils/slices.go +++ b/go/utils/slices.go @@ -1,14 +1,14 @@ package utils -func TypedSliceToInterfaceSlice[T any](s []T) []interface{} { - is := make([]interface{}, 0, len(s)) +func TypedSliceToInterfaceSlice[T any](s []T) []any { + is := make([]any, 0, len(s)) for _, t := range s { is = append(is, t) } return is } -func InterfaceSliceToTypedSlice[T any](s []interface{}) []T { +func InterfaceSliceToTypedSlice[T any](s []any) []T { ts := make([]T, 0, len(s)) for _, t := range s { ts = append(ts, t.(T)) @@ -16,6 +16,18 @@ func InterfaceSliceToTypedSlice[T any](s []interface{}) []T { return ts } +func InterfaceSliceContains[T comparable](s []any, o T) bool { + for _, _t := range s { + switch t := _t.(type) { + case T: + if t == o { + return true + } + } + } + return false +} + func RemoveFromUnorderedSlice[T comparable](s []T, o T) []T { for i, t := range s { if t != o { |
