aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2025-07-21 08:27:45 -0400
committerFlorian Fischer <florian.fischer@muhq.space>2025-07-22 12:54:28 -0400
commitf6d557d1ba93c7b578733a2d2da6c92f5e3c649c (patch)
treeccc029224388ec2443b46a44170fcc7052982dba
parent28a858ce697af5f23c3e2ce8b200d4ae4db1a90a (diff)
downloadmuhqs-game-f6d557d1ba93c7b578733a2d2da6c92f5e3c649c.tar.gz
muhqs-game-f6d557d1ba93c7b578733a2d2da6c92f5e3c649c.zip
implement "you control" target constraint
-rw-r--r--go/game/action.go6
-rw-r--r--go/game/action_test.go42
-rw-r--r--go/game/targets.go29
-rw-r--r--go/utils/slices.go18
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 {