diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2023-02-24 13:48:33 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-08-20 15:52:12 +0200 |
| commit | f58057564800cf45f61e3ab7c410385fcbf316df (patch) | |
| tree | 1babd0b9326ef72e842f0c2cecb2dda36a624213 /go | |
| parent | d46f56fa0d8f2217cf9716839ee83b93f2ee8485 (diff) | |
| download | muhqs-game-f58057564800cf45f61e3ab7c410385fcbf316df.tar.gz muhqs-game-f58057564800cf45f61e3ab7c410385fcbf316df.zip | |
intermeditae commit
* implement dopping piles
* implement simple spell targeting
* add some simple spells
Diffstat (limited to 'go')
| -rw-r--r-- | go/TODO | 3 | ||||
| -rw-r--r-- | go/client/game.go | 1 | ||||
| -rw-r--r-- | go/game/action.go | 50 | ||||
| -rw-r--r-- | go/game/card.go | 4 | ||||
| -rw-r--r-- | go/game/cardImplementations.go | 22 | ||||
| -rw-r--r-- | go/game/cardParsing.go | 6 | ||||
| -rw-r--r-- | go/game/permanent.go | 16 | ||||
| -rw-r--r-- | go/game/player.go | 6 | ||||
| -rw-r--r-- | go/game/state.go | 16 | ||||
| -rw-r--r-- | go/game/targets.go | 117 | ||||
| -rw-r--r-- | go/ui/collection.go | 4 |
11 files changed, 202 insertions, 43 deletions
@@ -1,6 +1,7 @@ +* Fix data race between UI and game loop (possible big state lock) * implement different highlighting used for selection and candidates * implement representation of next target and current seelction in prompt -* implement pile dropping +* implement artifact play equipped to next unit * implement artifcat action * move * implement equipment actions diff --git a/go/client/game.go b/go/client/game.go index 8d7fa74b..8dc69b25 100644 --- a/go/client/game.go +++ b/go/client/game.go @@ -544,7 +544,6 @@ func (g *Game) Update() error { g.updateHighlight() g.updatePassButton() return nil - } func (g *Game) Draw(screen *ebiten.Image) { diff --git a/go/game/action.go b/go/game/action.go index b8bc8d6e..09c3ec68 100644 --- a/go/game/action.go +++ b/go/game/action.go @@ -79,6 +79,50 @@ func (a *ActionBase) CheckTargets(s *State) error { return a.targets.CheckTargets(s) } +type PileDropAction struct { + ActionBase + pile []Permanent +} + +func (a *PileDropAction) String() string { + perm := a.source.(Permanent) + controller := perm.Controller() + if a.targets.RequireSelection() { + return fmt.Sprintf("%s drop %v", controller.Name, perm.Tile().Position) + } + + s := fmt.Sprintf("%s drop [", controller.Name) + pile := perm.Pile() + for i, t := range a.targets.ts { + s = fmt.Sprintf("%s %v@%v,", s, pile[i], t.sel[0]) + } + return s[:len(s)-1] + " ]" +} + +func (*PileDropAction) PayCosts(*State) bool { return true } + +func newPileDropAction(perm Permanent, tile *Tile, pile []Permanent) *PileDropAction { + a := &PileDropAction{ + ActionBase{ + source: perm, + }, + pile, + } + + a.resolveFunc = func(s *State) { + for i, p := range pile { + p.onDrop(p) + p.setContainingPerm(nil) + enterTileOrPile(p, a.targets.ts[i].sel[0].(*Tile)) + } + + perm.clearPile() + } + + a.targets = newPileDropTargets(perm.Controller().gameState, a) + return a +} + type PlayAction struct { ActionBase } @@ -112,10 +156,8 @@ func NewPlayAction(p *Player, c *Card, args ...interface{}) *PlayAction { s := p.gameState if c.IsPermanent() { a.targets = newTargets(newTarget(s, permanentPlayActionTarget, a)) - } else { - // TODO: implement parsing targets to play a card - // a.targets = newTargets(s, c.Impl.playTargets(s, p), a) - a.targets = newTargets() + } else if targetDesc := c.Impl.playTargets(); targetDesc != nil { + a.targets = newTargets(newTarget(s, *targetDesc, a)) } a.resolveFunc = func(s *State) { diff --git a/go/game/card.go b/go/game/card.go index d9d2c81b..914ccb56 100644 --- a/go/game/card.go +++ b/go/game/card.go @@ -94,6 +94,8 @@ type cardImplementation interface { fullActions(*Unit) []*FullAction freeActions(Permanent) []*FreeAction + playTargets() *TargetDesc + stateBasedActions(*State, Permanent) additionalSpawnsFor(Permanent, CardType) []*Tile @@ -116,6 +118,8 @@ func (*cardImplementationBase) spawnTiles(*State, *Player) []*Tile { return nil func (*cardImplementationBase) fullActions(*Unit) []*FullAction { return nil } func (*cardImplementationBase) freeActions(Permanent) []*FreeAction { return nil } +func (*cardImplementationBase) playTargets() *TargetDesc { return nil } + func (*cardImplementationBase) stateBasedActions(*State, Permanent) {} func (*cardImplementationBase) additionalSpawnsFor(Permanent, CardType) []*Tile { return nil } diff --git a/go/game/cardImplementations.go b/go/game/cardImplementations.go index 67b52315..bc2f7414 100644 --- a/go/game/cardImplementations.go +++ b/go/game/cardImplementations.go @@ -172,6 +172,25 @@ func (*wormtongueImpl) fullActions(u *Unit) []*FullAction { return []*FullAction{a} } +// ====== Magic Set ====== + +type dieImpl struct{ cardImplementationBase } + +func (*dieImpl) playTargets() *TargetDesc { + desc := newTargetDesc("unit") + return &desc +} + +func (*dieImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { + s.destroyPermanent(t.Cur().sel[0].(Permanent)) +} + +type ritualImpl struct{ cardImplementationBase } + +func (*ritualImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { + p.gainResource(3) +} + // ====== Nautics Set ====== var fishTrapAoE areaEffect = newGrantFullActionEffect("nautics/fisher", @@ -432,6 +451,9 @@ func init() { "base/tower_shield": &towerShieldImpl{}, "base/wormtongue": &wormtongueImpl{}, + "magic/die!": &dieImpl{}, + "magic/ritual!": &ritualImpl{}, + "nautics/captain": &captainImpl{}, "nautics/fish_trap": &fishTrapImpl{aoe: fishTrapAoE}, "nautics/fisher": &fisherImpl{}, diff --git a/go/game/cardParsing.go b/go/game/cardParsing.go index 4371afd7..0fde5a59 100644 --- a/go/game/cardParsing.go +++ b/go/game/cardParsing.go @@ -13,6 +13,8 @@ type dynamicCardImplementation struct { genFullActions func(*Unit) []*FullAction genFreeActions func(Permanent) []*FreeAction + targetDesc *TargetDesc + _stateBasedActions func(*State, Permanent) _additionalSpawnsFor func(Permanent, CardType) []*Tile @@ -46,6 +48,10 @@ func (impl *dynamicCardImplementation) freeActions(p Permanent) []*FreeAction { return nil } +func (impl *dynamicCardImplementation) playTargets() *TargetDesc { + return impl.targetDesc +} + func (impl *dynamicCardImplementation) stateBasedActions(s *State, p Permanent) { if impl._stateBasedActions != nil { impl._stateBasedActions(s, p) diff --git a/go/game/permanent.go b/go/game/permanent.go index 78966cfe..ae94a657 100644 --- a/go/game/permanent.go +++ b/go/game/permanent.go @@ -188,15 +188,6 @@ func removePermanentFromPile(containing, rm Permanent) { rm.setContainingPerm(nil) } -func dropPile(containing Permanent) { - for _, p := range containing.Pile() { - p.onDrop(p) - p.setContainingPerm(nil) - } - - containing.clearPile() -} - func enterTile(p Permanent, t *Tile) { p.SetTile(t) t.entering(p) @@ -212,6 +203,13 @@ func enterTileOrPile(p Permanent, t *Tile) { } } +func _leaveTile(p Permanent) { + if p.Tile() != nil { + p.Card().Impl.onLeaving(p.Tile()) + p.Tile().leaving(p) + } +} + func leaveTile(p Permanent) { if p.Tile() != nil { p.Card().Impl.onLeaving(p.Tile()) diff --git a/go/game/player.go b/go/game/player.go index 67a77a10..685de680 100644 --- a/go/game/player.go +++ b/go/game/player.go @@ -98,7 +98,7 @@ func (p *Player) upkeep() { a = newUpkeepAction(p) } - p.gameState.DeclareAction(a) + p.gameState.declareAction(a) p.gameState.Stack.resolve() } @@ -108,7 +108,7 @@ func (p *Player) actionPhase() { if _, ok := a.(*PassPriority); ok { return } - p.gameState.DeclareAction(a) + p.gameState.declareAction(a) p.gameState.Stack.resolve() } } @@ -125,7 +125,7 @@ func (p *Player) buyPhase() { } if a.Targets().HasSelections() { - s.DeclareAction(a) + s.declareAction(a) s.Stack.resolve() } } diff --git a/go/game/state.go b/go/game/state.go index 0ef8c4d4..82dccf15 100644 --- a/go/game/state.go +++ b/go/game/state.go @@ -264,7 +264,7 @@ func (s *State) pushAction(a Action) { s.Stack.push(a) } -func (s *State) DeclareAction(a Action) { +func (s *State) declareAction(a Action) { var err error err = a.CheckTargets(s) @@ -307,7 +307,7 @@ func (s *State) allPassing(skipFirst bool) bool { case *PassPriority: log.Printf("%s passed %d/%d\n", p.Name, i+1, nPlayers) default: - s.DeclareAction(a) + s.declareAction(a) return false } } @@ -382,13 +382,23 @@ func removePtr[P *Unit](collection []P, ptr P) []P { } func (s *State) destroyPermanent(p Permanent) { - leaveTile(p) + t := TileOrContainingPermTile(p) + + _leaveTile(p) s.removePermanent(p) switch p := p.(type) { case *Unit: s.Units = removePtr(s.Units, p) } + + if pile := p.Pile(); len(pile) > 0 { + dropAction := newPileDropAction(p, t, pile) + prompt(p.Controller().Ctrl, + newTargetSelectionPrompt(dropAction, fmt.Sprintf("Drop %v's pile", p))) + s.declareAction(dropAction) + } + p.Owner().DiscardPile.AddCard(p.Card()) } diff --git a/go/game/targets.go b/go/game/targets.go index df522565..5bc6aa9f 100644 --- a/go/game/targets.go +++ b/go/game/targets.go @@ -76,6 +76,16 @@ func newTarget(s *State, desc TargetDesc, action Action) *Target { return newTargetWithSel(s, desc, action, []interface{}{}) } +func newConstraintTarget(s *State, desc TargetDesc, constraint TargetConstraintFunc, action Action, +) *Target { + return &Target{ + s: s, + desc: desc.target, + constraint: constraint, + requirement: desc.requirement(), + } +} + func newTargets(targets ...*Target) *Targets { return &Targets{0, targets} } @@ -84,6 +94,26 @@ func newTargetDesc(desc string) TargetDesc { return TargetDesc{desc, "1"} } +func newPileDropTargets(s *State, a *PileDropAction) *Targets { + pile := a.pile + + targets := newTargets() + targets.ts = make([]*Target, 0, len(pile)) + + desc := newTargetDesc("adjacent tile") + for i, p := range pile { + constraints := parseTileTargetConstraint(desc.target, s, a) + // The selected tile must be available for the individual dropped card + constraints = append(constraints, availableTileConstraint(a, p.Card())) + // The selected tile must be not be previously selected in this action + constraints = append(constraints, noPreviousSelectionConstraint(targets, i)) + + targets.ts = append(targets.ts, newConstraintTarget(s, desc, conjunction(constraints...), a)) + } + + return targets +} + func (d *TargetDesc) requirement() TargetRequirement { switch d.req { case "?": @@ -125,6 +155,7 @@ func (t *Target) RequireSelection() bool { return len(t.sel) < t.requirement.m func (t *Target) HasSelection() bool { return len(t.sel) > 0 } func (t *Target) AllowSelection() bool { return len(t.sel) < t.requirement.max } func (t *Target) ClearSelection() { t.sel = []interface{}{} } +func (t *Target) NoSelection() bool { return len(t.sel) == 0 } func (t *Target) Selection() []interface{} { return t.sel } func (t *Target) Options() (options []interface{}) { @@ -173,6 +204,15 @@ func (t *Targets) ClearSelections() { t.ClearSelection() } } + +func (t *Targets) NoSelections() bool { + for _, t := range t.ts { + if !t.NoSelection() { + return false + } + } + return true +} func (t *Targets) Options() (options []interface{}) { return t.ts[t.idx].Options() } func (t *Targets) Cur() *Target { return t.ts[t.idx] } func (t *Targets) Next() { t.idx++ } @@ -198,6 +238,43 @@ func (targets *Targets) RequireSelection() bool { return false } +func conjunction(constraints ...TargetConstraintFunc) TargetConstraintFunc { + return func(t interface{}) error { + for _, constraint := range constraints { + if err := constraint(t); err != nil { + return err + } + } + + return nil + } +} + +func disjunction(constraints ...TargetConstraintFunc) TargetConstraintFunc { + return func(t interface{}) error { + var err error + for _, constraint := range constraints { + if err = constraint(t); err == nil { + return nil + } + } + + return err + } +} + +func noPreviousSelectionConstraint(targets *Targets, idx int) TargetConstraintFunc { + return func(t interface{}) error { + for i := 0; i < idx && i < targets.idx; i++ { + target := targets.ts[i] + if slices.Contains(target.sel, t) { + return fmt.Errorf("Selection %v already selected for target %d", t, i) + } + } + return nil + } +} + func targetConstraint(desc string, s *State, action Action) TargetConstraintFunc { constraints := []TargetConstraintFunc{} if strings.Contains(desc, "permanent") { @@ -216,15 +293,7 @@ func targetConstraint(desc string, s *State, action Action) TargetConstraintFunc // constraints = append(constraints, parseSpellTargetConstraint(desc, s, action)...) } - return func(t interface{}) error { - for _, constraint := range constraints { - if err := constraint(t); err != nil { - return err - } - } - - return nil - } + return conjunction(constraints...) } func enemyPermanentTargetConstraint(action Action) TargetConstraintFunc { @@ -272,7 +341,7 @@ func posFromTileOrPermanent(tileOrPermanent interface{}) Position { case *Tile: return obj.Position case Permanent: - return obj.Tile().Position + return TileOrContainingPermTile(obj).Position default: log.Panicf("Unhandled source type %T in posFromTileOrPermanent", tileOrPermanent) return INVALID_POSITION() @@ -376,6 +445,21 @@ func relaxedTileTarget(action Action, t interface{}) *Tile { return nil } +func availableTileConstraint(action Action, card *Card) TargetConstraintFunc { + return func(t interface{}) (err error) { + tile := relaxedTileTarget(action, t) + + if card == nil { + log.Panicf("Not implemented availability tile constraint for %T", action) + } + + if tile.IsAvailableForCard(card) { + return nil + } + return fmt.Errorf("tile %v is not available for %s", tile, card.Name) + } +} + func parseTileTargetConstraint(desc string, s *State, action Action) []TargetConstraintFunc { constraints := []TargetConstraintFunc{} @@ -422,18 +506,7 @@ func parseTileTargetConstraint(desc string, s *State, action Action) []TargetCon } if strings.Contains(desc, "available") { - constraints = append(constraints, func(t interface{}) (err error) { - tile := relaxedTileTarget(action, t) - - if card == nil { - log.Panicf("Not implemented availability tile constraint for %T", action) - } - - if tile.IsAvailableForCard(card) { - return nil - } - return fmt.Errorf("tile %v is not available for %s", tile, card.Name) - }) + constraints = append(constraints, availableTileConstraint(action, card)) } if strings.Contains(desc, "water") { diff --git a/go/ui/collection.go b/go/ui/collection.go index 027d0d99..03920f34 100644 --- a/go/ui/collection.go +++ b/go/ui/collection.go @@ -5,6 +5,7 @@ import ( "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" + "golang.org/x/exp/slices" ) type Collection struct { @@ -33,6 +34,9 @@ func (c *Collection) AddWidget(w Widget) { if w == nil { log.Panicf("Adding nil widget to collection") } + if slices.Contains(c.widgets, w) { + log.Panicf("Double insertion of %v", w) + } c.widgets = append(c.widgets, w) } |
