aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2023-02-17 00:29:26 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-08-20 15:50:11 +0200
commit28defc1cad1a96a2954780efa01396db8bb2fe31 (patch)
treeb6bf6e43d48a7bc7f0ccce7d6f4253e1e15e6492
parent8f81177d191b3e6d80212e1fa370d37db2fe38ce (diff)
downloadmuhqs-game-28defc1cad1a96a2954780efa01396db8bb2fe31.tar.gz
muhqs-game-28defc1cad1a96a2954780efa01396db8bb2fe31.zip
implement unit and artifact switch action
Simplify the Choice Widget and make it a TextBox variant. Drive-by fixes: * ui.Collection: prioritize newer widgets during active widget selection * game.FullAction: fix incorrect cost function. Full actions cost all available actions. Also fix the cost check
-rw-r--r--go/TODO1
-rw-r--r--go/client/game.go15
-rw-r--r--go/game/action.go82
-rw-r--r--go/game/card.go9
-rw-r--r--go/game/permanent.go9
-rw-r--r--go/game/state.go11
-rw-r--r--go/game/unit.go13
-rw-r--r--go/ui/choice.go62
-rw-r--r--go/ui/collection.go5
9 files changed, 128 insertions, 79 deletions
diff --git a/go/TODO b/go/TODO
index f2cde287..fbcc92f8 100644
--- a/go/TODO
+++ b/go/TODO
@@ -1,7 +1,6 @@
* implement pile dropping
* implement artifcat action
* move
- * switch
* implement equipment actions
* equip
* drop
diff --git a/go/client/game.go b/go/client/game.go
index 8447e452..32fb5f2f 100644
--- a/go/client/game.go
+++ b/go/client/game.go
@@ -409,18 +409,12 @@ func (g *Game) highlightTargets() {
func (g *Game) Update() error {
g.handlePlayerNotifications()
-
- if err := g.Collection.Update(); err != nil {
- return nil
- }
-
- x, y := ebiten.CursorPosition()
-
- obj := g.findObjectAt(x, y)
-
g.highlightTargets()
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
+ x, y := ebiten.CursorPosition()
+ obj := g.findObjectAt(x, y)
+
if g.prompt != nil {
if err := g.prompt.Add(obj); err == nil {
g.AddHighlight(obj)
@@ -445,7 +439,8 @@ func (g *Game) Update() error {
g.prompt.ClearSelections()
}
}
- return nil
+
+ return g.Collection.Update()
}
func (g *Game) Draw(screen *ebiten.Image) {
diff --git a/go/game/action.go b/go/game/action.go
index 77e9743f..1e25cd67 100644
--- a/go/game/action.go
+++ b/go/game/action.go
@@ -161,13 +161,7 @@ func NewMoveAction(u *Unit) *MoveAction {
u.move(s, tile)
}
- a.costFunc = func(*State) bool {
- if u.AvailMoveActions < 1 {
- return false
- }
- u.AvailMoveActions--
- return true
- }
+ a.costFunc = genMoveActionCost(u)
return a
}
@@ -182,6 +176,36 @@ func (a *MoveAction) String() string {
return fmt.Sprintf("%s -> %v", u.FmtWController(), t.Position)
}
+func genBaseActionCost(u *Unit, attack, move int) ActionCostFunc {
+ return func(*State) bool {
+ if u.AvailAttackActions < attack || u.AvailMoveActions < move {
+ return false
+ }
+ u.AvailAttackActions -= attack
+ u.AvailMoveActions -= move
+ return true
+ }
+}
+
+func genAttackActionCost(u *Unit) ActionCostFunc {
+ return genBaseActionCost(u, 1, 0)
+}
+
+func genMoveActionCost(u *Unit) ActionCostFunc {
+ return genBaseActionCost(u, 0, 1)
+}
+
+func genFullActionCost(u *Unit) ActionCostFunc {
+ return func(*State) bool {
+ if u.AvailAttackActions < 1 || u.AvailMoveActions < 1 {
+ return false
+ }
+ u.AvailAttackActions = 0
+ u.AvailMoveActions = 0
+ return true
+ }
+}
+
type AttackAction struct {
ActionBase
}
@@ -200,13 +224,7 @@ func NewAttackAction(u *Unit) *AttackAction {
s.Fight(u, a.Target().sel[0].(Permanent))
}
- a.costFunc = func(*State) bool {
- if u.AvailAttackActions < 1 {
- return false
- }
- u.AvailAttackActions--
- return true
- }
+ a.costFunc = genAttackActionCost(u)
return a
}
@@ -221,6 +239,33 @@ func (a *AttackAction) String() string {
return fmt.Sprintf("%s x %v", u.FmtWController(), target.Tile().Position)
}
+type ArtifactSwitchAction struct {
+ ActionBase
+ Artifact Permanent
+}
+
+func newArtifactSwitchAction(u *Unit, artifact Permanent) *ArtifactSwitchAction {
+ a := &ArtifactSwitchAction{
+ ActionBase{
+ source: u,
+ Card: u.Card(),
+ },
+ artifact,
+ }
+
+ a.resolveFunc = func(s *State) {
+ s.switchPermanents(a.Source().(*Unit), a.Artifact)
+ }
+
+ a.costFunc = genMoveActionCost(u)
+
+ return a
+}
+
+func (a *ArtifactSwitchAction) String() string {
+ return fmt.Sprintf("%s <-> %v", a.Source().(*Unit).FmtWController(), a.Artifact)
+}
+
type FullAction struct {
ActionBase
desc string
@@ -242,14 +287,7 @@ func NewFullAction(u *Unit, proto ActionFuncPrototype, desc string) *FullAction
rf(s)
}
- a.costFunc = func(*State) bool {
- if u.AvailAttackActions < 1 && u.AvailMoveActions < 1 {
- return false
- }
- u.AvailAttackActions--
- u.AvailMoveActions--
- return true
- }
+ a.costFunc = genFullActionCost(u)
return a
}
diff --git a/go/game/card.go b/go/game/card.go
index f305634b..d9d2c81b 100644
--- a/go/game/card.go
+++ b/go/game/card.go
@@ -80,6 +80,15 @@ func (ct CardType) IsPermanent() bool {
}
}
+func (ct CardType) IsArtifact() bool {
+ switch ct {
+ case CardTypes.Artifact, CardTypes.Equipment, CardTypes.Potion:
+ return true
+ default:
+ return false
+ }
+}
+
type cardImplementation interface {
spawnTiles(*State, *Player) []*Tile
fullActions(*Unit) []*FullAction
diff --git a/go/game/permanent.go b/go/game/permanent.go
index db2dcaff..cd5cbfe6 100644
--- a/go/game/permanent.go
+++ b/go/game/permanent.go
@@ -203,6 +203,15 @@ func enterTile(p Permanent, t *Tile) {
p.Card().Impl.onEntering(t)
}
+func enterTileOrPile(p Permanent, t *Tile) {
+ if t.Permanent == nil {
+ enterTile(p, t)
+ } else {
+ AddPermanentToPile(t.Permanent, p)
+ p.SetTile(nil)
+ }
+}
+
func leaveTile(p Permanent) {
if p.Tile() != nil {
p.Card().Impl.onLeaving(p.Tile())
diff --git a/go/game/state.go b/go/game/state.go
index 7fa6e1bf..4af56b10 100644
--- a/go/game/state.go
+++ b/go/game/state.go
@@ -362,6 +362,17 @@ func (s *State) MoveUnit(u *Unit, t *Tile) {
u.move(s, t)
}
+func (s *State) switchPermanents(p1 Permanent, p2 Permanent) {
+ t1 := TileOrContainingPermTile(p1)
+ t2 := TileOrContainingPermTile(p2)
+
+ leaveTile(p1)
+ leaveTile(p2)
+
+ enterTileOrPile(p1, t2)
+ enterTileOrPile(p2, t1)
+}
+
func (s *State) FilterUnits(filter func(*Unit) bool) []*Unit {
units := []*Unit{}
for _, u := range s.Units {
diff --git a/go/game/unit.go b/go/game/unit.go
index 51383b77..bc9cad3f 100644
--- a/go/game/unit.go
+++ b/go/game/unit.go
@@ -99,12 +99,7 @@ func (u *Unit) move(s *State, tile *Tile) {
leaveTile(u)
}
- if tile.Permanent == nil {
- enterTile(u, tile)
- } else { // crew permanent at tile
- AddPermanentToPile(tile.Permanent, u)
- u.tile = nil
- }
+ enterTileOrPile(u, tile)
}
func (u *Unit) MoveRangeTiles(m *Map) []*Tile {
@@ -200,6 +195,12 @@ func (u *Unit) AvailSlowActions() (actions []Action) {
for _, p := range u.FullActions {
actions = append(actions, p)
}
+
+ for _, t := range TilesInRange(m, u, 1) {
+ if t.Permanent != nil && t.Permanent.Card().Type.IsArtifact() {
+ actions = append(actions, newArtifactSwitchAction(u, t.Permanent))
+ }
+ }
}
return actions
diff --git a/go/ui/choice.go b/go/ui/choice.go
index 28878604..04b8f725 100644
--- a/go/ui/choice.go
+++ b/go/ui/choice.go
@@ -9,66 +9,50 @@ import (
)
type Choice struct {
- X, Y int
- Width, choiceHeight int
- choices []string
- onClick func(*Choice, int, int)
- buttons []Button
+ TextBox
+ choices []interface{}
+ onClick func(*Choice, int, int)
}
func choiceButtonOnClickDummy(b *SimpleButton) {}
-func NewChoice(x, y int, width, choiceHeight int, choices []string,
- onClick func(*Choice, int, int),
-) *Choice {
- c := &Choice{x, y, width, choiceHeight, choices, onClick, []Button{}}
-
- for i, choice := range c.choices {
- b := NewSimpleButton(c.X, c.Y+i*c.choiceHeight, c.Width, c.choiceHeight, choice,
- choiceButtonOnClickDummy)
+func NewChoice(x, y int, choices []interface{}, onClick func(*Choice, int, int)) *Choice {
+ c := &Choice{
+ TextBox: *(NewAutoTextBox(x, y, "")),
+ choices: choices,
+ onClick: onClick,
+ }
- c.buttons = append(c.buttons, b)
+ c.renderImpl = func() *ebiten.Image {
+ c.setText()
+ return c.render()
}
return c
}
-func (c *Choice) Draw(screen *ebiten.Image) {
- for _, b := range c.buttons {
- b.Draw(screen)
+func (c *Choice) setText() {
+ c.Width, c.Height = -1, -1
+ t := ""
+ for _, choice := range c.choices {
+ t = fmt.Sprintf("%s%v\n", t, choice)
}
+
+ c.text = t[:len(t)-1]
}
func (c *Choice) GetChoosen(x, y int) int {
- return (y - c.Y) / c.choiceHeight
+ i := (y - c.Y) / (c.Height / len(c.choices))
+ return i
}
func (c *Choice) Click(x, y int) {
c.onClick(c, x, y)
}
-func (c *Choice) Contains(x, y int) bool {
- height := len(c.choices) * c.choiceHeight
- return x >= c.X && x <= c.X+c.Width && y >= c.Y && y <= c.Y+height
-}
-
-func (c *Choice) FindObjectAt(x, y int) interface{} { return nil }
-func (c *Choice) Render() *ebiten.Image { return nil }
-
-func NewObjectChoice(x, y int, objs []interface{}, onClick func(*Choice, int, int)) *Choice {
- // TODO: use dynamic with width and height
- width := 200
- choiceHeight := 40
- labels := make([]string, 0, len(objs))
- for _, obj := range objs {
- labels = append(labels, fmt.Sprintf("%v", obj))
- }
- return NewChoice(x, y, width, choiceHeight, labels, onClick)
-}
-
func NewActionChoice(x, y int, actions []game.Action, onClick func(*Choice, int, int)) *Choice {
- return NewObjectChoice(x, y, utils.TypedSliceToInterfaceSlice(actions), onClick)
+ return NewChoice(x, y, utils.TypedSliceToInterfaceSlice(actions), onClick)
}
func NewPermChoice(x, y int, perms []game.Permanent, onClick func(*Choice, int, int)) *Choice {
- return NewObjectChoice(x, y, utils.TypedSliceToInterfaceSlice(perms), onClick)
+ return NewChoice(x, y, utils.TypedSliceToInterfaceSlice(perms), onClick)
}
diff --git a/go/ui/collection.go b/go/ui/collection.go
index d75e3267..027d0d99 100644
--- a/go/ui/collection.go
+++ b/go/ui/collection.go
@@ -69,7 +69,10 @@ func (c *Collection) Update() error {
x, y := ebiten.CursorPosition()
var activeWidget Widget
- for _, w := range c.widgets {
+ // Iterate the widget in reverse order to ensure that upper widgets are
+ // considered first
+ for i := len(c.widgets) - 1; i >= 0; i-- {
+ w := c.widgets[i]
if !w.Contains(x, y) {
continue
}