aboutsummaryrefslogtreecommitdiff
path: root/go
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2023-02-24 13:48:33 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-08-20 15:52:12 +0200
commitf58057564800cf45f61e3ab7c410385fcbf316df (patch)
tree1babd0b9326ef72e842f0c2cecb2dda36a624213 /go
parentd46f56fa0d8f2217cf9716839ee83b93f2ee8485 (diff)
downloadmuhqs-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/TODO3
-rw-r--r--go/client/game.go1
-rw-r--r--go/game/action.go50
-rw-r--r--go/game/card.go4
-rw-r--r--go/game/cardImplementations.go22
-rw-r--r--go/game/cardParsing.go6
-rw-r--r--go/game/permanent.go16
-rw-r--r--go/game/player.go6
-rw-r--r--go/game/state.go16
-rw-r--r--go/game/targets.go117
-rw-r--r--go/ui/collection.go4
11 files changed, 202 insertions, 43 deletions
diff --git a/go/TODO b/go/TODO
index a868da71..1aa04869 100644
--- a/go/TODO
+++ b/go/TODO
@@ -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)
}