diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-08-19 15:37:09 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-08-20 15:57:40 +0200 |
| commit | 3d36f5c92ec204f72d55df5b41e9bacdf5e87ba8 (patch) | |
| tree | 25f8ce5b91b34e6f56380f1c1a32fcd08ff8d419 | |
| parent | 790706dbf1e797607dfb6c6717ab863a9e4cb5ca (diff) | |
| download | muhqs-game-3d36f5c92ec204f72d55df5b41e9bacdf5e87ba8.tar.gz muhqs-game-3d36f5c92ec204f72d55df5b41e9bacdf5e87ba8.zip | |
fix spawn tile constraint for water spawns
This is not a general solution because spawns could be a valid tile
type constraint as well as meaning available spawn tiles.
The separation and a clean implementation is still a TODO.
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | go/game/targets.go | 16 | ||||
| -rw-r--r-- | go/game/targets_test.go | 67 |
3 files changed, 81 insertions, 3 deletions
@@ -125,3 +125,4 @@ Things that should be done eventually: - [ ] finish error handling for remote PlayerControls - [ ] support big card grids (for sealed) - [ ] implement replacement effects +- [ ] distinguish between a spawn tile type constraint and available spawn tiles diff --git a/go/game/targets.go b/go/game/targets.go index e056a5c0..05d690c0 100644 --- a/go/game/targets.go +++ b/go/game/targets.go @@ -3,11 +3,11 @@ package game import ( "errors" "fmt" - "log" "strconv" "strings" "golang.org/x/exp/slices" + "muhq.space/muhqs-game/go/log" "muhq.space/muhqs-game/go/utils" ) @@ -215,6 +215,8 @@ func (t *Target) Options() (options []any) { for _, candidate := range candidates { if err := t.constraint(candidate); err == nil { options = append(options, candidate) + } else { + log.Debug("no valid option", "candidate", candidate, "error", err) } } return options @@ -294,6 +296,7 @@ func (t *Targets) Next() { t.idx++ } func (t *Targets) Targets() []*Target { return t.ts } func (t *Targets) CheckTargets(s *LocalState) error { + log.Debug("Check all sub targets", "targets", t.ts) for _, target := range t.ts { if err := target.CheckSelection(s); err != nil { return err @@ -627,6 +630,7 @@ func availableTileConstraint(action Action, card *Card) TargetConstraintFunc { if tile.IsAvailableForCard(card) { return nil } + log.Debug("tile not available", "tile", tile, "card", card) return fmt.Errorf("tile %v is not available for %s", tile, card.Name) } } @@ -682,7 +686,10 @@ func parseTileTargetConstraint(desc string, s *LocalState, action Action) []Targ constraints = append(constraints, func(t any) (err error) { tile := relaxedTileTarget(action, t) - if slices.Contains(s.AvailableSpawnTiles(player, card), tile) { + availableSpawnTiles := s.AvailableSpawnTiles(player, card) + log.Debug("check possible spawn tile", "tile", tile, "spawns", availableSpawnTiles) + if slices.Contains(availableSpawnTiles, tile) { + log.Debug("is spawn tile", "tile", tile) return nil } return fmt.Errorf("tile %v is no spawn tile", tile) @@ -725,6 +732,11 @@ func parseTileTargetConstraint(desc string, s *LocalState, action Action) []Targ for name := range TileNames { if strings.Contains(desc, name) { + // FIXME: skip spawn tile type constraint since it colides with available water spawns + if name == "spawn" { + continue + } + tileType := TileNames[name] if strings.Contains(desc, "connected") { // Find connected tiles diff --git a/go/game/targets_test.go b/go/game/targets_test.go index ae451dbf..eed94f4f 100644 --- a/go/game/targets_test.go +++ b/go/game/targets_test.go @@ -9,7 +9,7 @@ func TestTileTargets(t *testing.T) { mapDef := `map: |1- HSTS HSFS - TSWS + TSWD symbols: T: tower @@ -17,6 +17,7 @@ symbols: F: farm S: street W: deep water + D: docks ` s := NewLocalState() r := strings.NewReader(mapDef) @@ -72,6 +73,70 @@ symbols: if len(opts) != 6 { t.Fatal("expected 6 available candidates not:", len(opts)) } + + fisher := NewCard("nautics/fisher") + p.Hand.AddCard(fisher) + pa := NewPlayAction(p, fisher) + opts = pa.Target().Options() + if len(opts) != 1 { + t.Fatal("expected 1 available candidates not:", len(opts)) + } +} + +func TestSpawnTargets(t *testing.T) { + s := NewLocalState() + m, _ := GetMap("the-kraken") + s.SetMap(m) + + s.activePhase = actionPhase + + p := s.AddNewPlayer("player", NewDeck()) + ctrl := newMockPlayerControl(p) + ctrl.actionToSend = NewPassPriority(p) + p.Ctrl = ctrl + + p.gainResource(2) + + farmer := NewCard("misc/farmer") + p.Hand.AddCard(farmer) + fisher := NewCard("nautics/fisher") + p.Hand.AddCard(fisher) + + pa := NewPlayAction(p, farmer) + if !pa.Target().RequireSelection() { + t.Fatal("playing farmer has not require selection") + } + if !pa.Target().AllowSelection() { + t.Fatal("playing farmer has no valid targets") + } + farmerPos := Position{3, 13} + err := pa.Target().AddSelection(m.TileAt(farmerPos)) + if err != nil { + t.Fatal("Not allowed to play farmer at:", farmerPos) + } + s.declareAction(pa) + if s.stack.IsEmpty() { + t.Fatal("farmer play action not declared") + } + s.stack.resolve() + + pa2 := NewPlayAction(p, fisher) + if !pa2.Target().RequireSelection() { + t.Fatal("playing fisher has not require selection") + } + if !pa2.Target().AllowSelection() { + t.Fatal("playing fisher has no valid targets") + } + fisherPos := Position{3, 10} + pa2.Target().AddSelection(m.TileAt(fisherPos)) + if err != nil { + t.Fatal("Not allowed to play fisher at:", fisherPos) + } + s.declareAction(pa2) + if s.stack.IsEmpty() { + t.Fatal("fisher play action not declared") + } + s.stack.resolve() } func TestFlexAttackTargets(t *testing.T) { |
