aboutsummaryrefslogtreecommitdiff
path: root/go/game
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2023-02-01 18:46:32 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-01-27 16:43:45 +0100
commit21a87ffccae9950580a240180fd3703768ef450e (patch)
tree1cb83289216bd1e213d417812b4e09001ca89c09 /go/game
parent3fd15c9b83fff074b588ff9688f9e49aab38cd32 (diff)
downloadmuhqs-game-21a87ffccae9950580a240180fd3703768ef450e.tar.gz
muhqs-game-21a87ffccae9950580a240180fd3703768ef450e.zip
intermediate commit
Diffstat (limited to 'go/game')
-rw-r--r--go/game/action.go303
-rw-r--r--go/game/ai.go43
-rw-r--r--go/game/artifact.go4
-rw-r--r--go/game/card.go8
-rw-r--r--go/game/cardImplementations.go33
-rw-r--r--go/game/cardParsing.go33
-rw-r--r--go/game/kraken.go55
-rw-r--r--go/game/permanent.go7
-rw-r--r--go/game/state.go70
-rw-r--r--go/game/targets.go307
-rw-r--r--go/game/tile.go5
-rw-r--r--go/game/unit.go85
12 files changed, 717 insertions, 236 deletions
diff --git a/go/game/action.go b/go/game/action.go
index 0f7a1294..4894d47d 100644
--- a/go/game/action.go
+++ b/go/game/action.go
@@ -4,40 +4,52 @@ import (
"fmt"
)
-type ActionPrototype interface {
- Specialize(*State, ...interface{}) Action
- String() string
-}
+type (
+ ActionCostFunc func(*State) bool
+ ActionResolveFunc func(*State)
+ ActionFuncPrototype func(Action) ActionResolveFunc
+)
type Action interface {
- Resolve(s *State)
+ Source() interface{}
+ Targets() *Targets
+
+ CheckTargets(*State) error
PayCosts(s *State) bool
+ Resolve(s *State)
String() string
}
-type PassPriority struct{ Source *Player }
+type PassPriority struct{ source *Player }
-func (*PassPriority) Resolve(*State) {}
-func (*PassPriority) PayCosts(*State) bool { return true }
-func (*PassPriority) String() string { return "pass" }
-func NewPassPriority(p *Player) Action { return &PassPriority{p} }
+func (a *PassPriority) Source() interface{} { return a.source }
+func (*PassPriority) Targets() *Targets { return nil }
+func (*PassPriority) CheckTargets(*State) error { return nil }
+func (*PassPriority) PayCosts(*State) bool { return true }
+func (*PassPriority) Resolve(*State) {}
+func (*PassPriority) String() string { return "pass" }
+func NewPassPriority(p *Player) Action { return &PassPriority{p} }
-type HandCardSelection struct {
- cards []*Card
-}
+type HandCardSelection struct{ cards []*Card }
-func (*HandCardSelection) Resolve(*State) {}
-func (*HandCardSelection) PayCosts(*State) bool { return true }
-func (*HandCardSelection) String() string { return "hand card selection" }
-func NewHandCardSelection(cards []*Card) Action { return &HandCardSelection{cards} }
+func (*HandCardSelection) Source() interface{} { return nil }
+func (*HandCardSelection) Targets() *Targets { return nil }
+func (*HandCardSelection) CheckTargets(*State) error { return nil }
+func (*HandCardSelection) PayCosts(*State) bool { return true }
+func (*HandCardSelection) Resolve(*State) {}
+func (*HandCardSelection) String() string { return "hand card selection" }
+func NewHandCardSelection(cards []*Card) Action { return &HandCardSelection{cards} }
type ActionBase struct {
- Source interface{}
+ source interface{}
Card *Card
- Targets interface{}
- resolveFunc func(s *State)
- costFunc func(s *State) bool
- desc string
+ targets *Targets
+ resolveFunc ActionResolveFunc
+ costFunc ActionCostFunc
+}
+
+func (a *ActionBase) Source() interface{} {
+ return a.source
}
func (a *ActionBase) Resolve(s *State) {
@@ -48,73 +60,91 @@ func (a *ActionBase) PayCosts(s *State) bool {
return a.costFunc(s)
}
-func (a *ActionBase) String() string {
- switch source := a.Source.(type) {
- case Permanent:
- p := source
- return fmt.Sprintf("%s: %s %s", p.Controller().Name, p.Tile().Position.String(), a.desc)
- case *Player:
- return fmt.Sprintf("%s %s", source.Name, a.desc)
- default:
- return fmt.Sprintf("%v %s", source, a.desc)
- }
+func (a *ActionBase) Targets() *Targets {
+ return a.targets
+}
+
+func (a *ActionBase) CheckTargets(s *State) error {
+ return a.targets.CheckTargets(s)
}
type PlayAction struct {
ActionBase
}
-func NewPlayAction(p *Player, c *Card, targets interface{}) Action {
- costFunc := func(*State) bool {
- cost := c.PlayCosts()
+const permanentPlayActionTarget = "available spawn tile"
- if p.Resource < cost {
- return false
+func NewPlayAction(p *Player, c *Card, args ...interface{}) *PlayAction {
+ a := &PlayAction{ActionBase{
+ source: p,
+ Card: c,
+ }}
+
+ if len(args) > 0 {
+ if costFunc, ok := args[0].(ActionCostFunc); ok {
+ a.costFunc = costFunc
}
- p.Resource -= cost
- return true
}
- return NewPlayActionWithSpecialCosts(p, c, targets, costFunc)
-}
-func NewPlayActionWithSpecialCosts(p *Player, c *Card, targets interface{},
- costFunc func(*State) bool,
-) Action {
+ if a.costFunc == nil {
+ a.costFunc = func(*State) bool {
+ cost := c.PlayCosts()
+
+ if p.Resource < cost {
+ return false
+ }
+ p.Resource -= cost
+ return true
+ }
+ }
+
+ s := p.gameState
+ if c.IsPermanent() {
+ a.targets = newTargets(s, []string{permanentPlayActionTarget}, a)
+ } else {
+ a.targets = newTargets(s, []string{}, a)
+ }
+
p.Hand.RemoveCard(c)
- resolveFunc := func(s *State) {
- s.ResolvePlay(p, c, targets)
+ a.resolveFunc = func(s *State) {
+ s.resolvePlay(p, c, a.targets)
}
- desc := fmt.Sprintf("play %s@%v", c.Name, targets)
- return &PlayAction{ActionBase{p, c, targets, resolveFunc, costFunc, desc}}
+ return a
}
-type MoveAction struct {
- ActionBase
-}
+func (a *PlayAction) String() string {
+ p := a.source.(*Player)
+ if a.targets.RequireTargets() {
+ return fmt.Sprintf(" %s play %s", p.Name, a.Card.Name)
+ }
-type MoveActionPrototype struct {
- Unit *Unit
+ return fmt.Sprintf("%s play %s@%v", p.Name, a.Card.Name, a.targets)
}
-func NewMoveActionPrototype(u *Unit) *MoveActionPrototype {
- return &MoveActionPrototype{u}
+type MoveAction struct {
+ ActionBase
}
-func (proto *MoveActionPrototype) String() string {
- return fmt.Sprintf("move %s", proto.Unit.Movement.String())
-}
+const moveActionTargetDesc = "available tile"
+
+func NewMoveAction(u *Unit) *MoveAction {
+ a := &MoveAction{
+ ActionBase{
+ source: u,
+ Card: u.Card(),
+ },
+ }
-func (proto *MoveActionPrototype) Specialize(s *State, args ...interface{}) Action {
- target := args[0].(*Tile)
- u := proto.Unit
+ a.targets = newTargets(u.Controller().gameState, []string{moveActionTargetDesc}, a)
- resolveFunc := func(s *State) {
- u.move(s, target)
+ a.resolveFunc = func(s *State) {
+ tile := a.targets.sel[0].(*Tile)
+ u.move(s, tile)
}
- costFunc := func(*State) bool {
+ a.costFunc = func(*State) bool {
if u.AvailMoveActions < 1 {
return false
}
@@ -122,35 +152,38 @@ func (proto *MoveActionPrototype) Specialize(s *State, args ...interface{}) Acti
return true
}
- desc := fmt.Sprintf("-> %s", target.Position.String())
- return &MoveAction{ActionBase{u, u.Card(), target, resolveFunc, costFunc, desc}}
+ return a
}
-type AttackAction struct {
- ActionBase
-}
+func (a *MoveAction) String() string {
+ u := a.source.(*Unit)
+ if a.targets.RequireTargets() {
+ return fmt.Sprintf("move %s", u.Movement.String())
+ }
-type AttackActionPrototype struct {
- Unit *Unit
+ t := a.targets.sel[0].(*Tile)
+ return fmt.Sprintf("%s: %s -> %s", u.Controller().Name, u.Card().Name, t.Position.String())
}
-func NewAttackActionPrototype(u *Unit) *AttackActionPrototype {
- return &AttackActionPrototype{u}
+type AttackAction struct {
+ ActionBase
}
-func (proto *AttackActionPrototype) String() string {
- return fmt.Sprintf("attack %s", proto.Unit.Attack.String())
-}
+const attackActionTargetDesc = "attackable permanent"
+
+func NewAttackAction(u *Unit) *AttackAction {
+ a := &AttackAction{ActionBase{
+ source: u,
+ Card: u.Card(),
+ }}
-func (proto *AttackActionPrototype) Specialize(s *State, args ...interface{}) Action {
- target := args[0].(Permanent)
- u := proto.Unit
+ a.targets = newTargets(u.Controller().gameState, []string{attackActionTargetDesc}, a)
- resolveFunc := func(s *State) {
- s.Fight(u, target)
+ a.resolveFunc = func(s *State) {
+ s.Fight(u, a.targets.sel[0].(Permanent))
}
- costFunc := func(*State) bool {
+ a.costFunc = func(*State) bool {
if u.AvailAttackActions < 1 {
return false
}
@@ -158,27 +191,41 @@ func (proto *AttackActionPrototype) Specialize(s *State, args ...interface{}) Ac
return true
}
- desc := fmt.Sprintf("x %s", target.Tile().Position.String())
- return &AttackAction{ActionBase{u, u.Card(), target, resolveFunc, costFunc, desc}}
+ return a
}
-type FullActionPrototype struct {
- Unit *Unit
- resolveFuncPrototype func(*State, *Unit) func(*State)
-}
+func (a *AttackAction) String() string {
+ u := a.source.(*Unit)
+ if a.targets.RequireTargets() {
+ return fmt.Sprintf("attack %s", u.Attack.String())
+ }
-func NewFullActionPrototype(u *Unit, proto func(*State, *Unit) func(*State)) *FullActionPrototype {
- return &FullActionPrototype{u, proto}
+ target := a.targets.sel[0].(Permanent)
+ return fmt.Sprintf("%s: %s x %s",
+ u.Controller().Name, u.Card().Name, target.Tile().Position.String())
}
-func (proto *FullActionPrototype) String() string {
- return "full_action"
+type FullAction struct {
+ ActionBase
+ desc string
}
-func (proto *FullActionPrototype) Specialize(s *State, args ...interface{}) Action {
- u := proto.Unit
- resolveFunc := proto.resolveFuncPrototype(s, u)
- costFunc := func(*State) bool {
+func NewFullAction(u *Unit, proto ActionFuncPrototype, desc string) *FullAction {
+ a := &FullAction{
+ ActionBase{
+ source: u,
+ Card: u.Card(),
+ },
+
+ desc,
+ }
+
+ a.resolveFunc = func(s *State) {
+ rf := proto(a)
+ rf(s)
+ }
+
+ a.costFunc = func(*State) bool {
if u.AvailAttackActions < 1 && u.AvailMoveActions < 1 {
return false
}
@@ -186,28 +233,50 @@ func (proto *FullActionPrototype) Specialize(s *State, args ...interface{}) Acti
u.AvailMoveActions--
return true
}
- desc := "full_action"
- return &ActionBase{u, u.Card(), nil, resolveFunc, costFunc, desc}
-}
-
-type FreeActionPrototype struct {
- Permanent Permanent
+ return a
}
-func (proto *FreeActionPrototype) String() string {
- return "free_action"
-}
+func (a *FullAction) String() string {
+ u := a.source.(*Unit)
+ if a.targets.RequireTargets() {
+ return fmt.Sprintf("↻ %s", a.desc)
+ }
-func (proto *FreeActionPrototype) Specialize(s *State, args ...interface{}) Action {
- p := proto.Permanent
- resolveFunc := args[0].(func(s *State))
- costFunc := args[1].(func(s *State) bool)
- desc := "free_action"
- return &FreeAction{ActionBase{p, p.Card(), nil, resolveFunc, costFunc, desc}}
+ return fmt.Sprintf("%s: %s ↻@%s", u.Controller(), u.Card().Name, a.targets)
}
type FreeAction struct {
ActionBase
+ desc string
+}
+
+func NewFreeAction(p Permanent, resolveProto ActionFuncPrototype, costFunc ActionCostFunc,
+ desc string,
+) *FreeAction {
+ a := &FreeAction{
+ ActionBase{
+ source: p,
+ Card: p.Card(),
+ costFunc: costFunc,
+ },
+ desc,
+ }
+
+ a.resolveFunc = func(s *State) {
+ rf := resolveProto(a)
+ rf(s)
+ }
+
+ return a
+}
+
+func (a *FreeAction) String() string {
+ p := a.source.(Permanent)
+ if a.targets.RequireTargets() {
+ return fmt.Sprintf("free_action %s", a.desc)
+ }
+
+ return fmt.Sprintf("%s: %s free_action@%s", p.Controller(), p.Card().Name, a.targets)
}
type BuyAction struct {
@@ -215,6 +284,18 @@ type BuyAction struct {
card *Card
}
+func (a *BuyAction) Source() interface{} {
+ return a.player
+}
+
+func (a *BuyAction) Targets() *Targets {
+ return newTargetsWithSel(a.player.gameState, []string{"store cards"}, a, []interface{}{a.card})
+}
+
+func (a *BuyAction) CheckTargets(s *State) error {
+ return s.isValidBuy(a)
+}
+
func (a *BuyAction) Resolve(s *State) {
a.player.Store.MoveCard(a.card, a.player.DiscardPile)
}
diff --git a/go/game/ai.go b/go/game/ai.go
index 01820a19..0adce722 100644
--- a/go/game/ai.go
+++ b/go/game/ai.go
@@ -138,17 +138,18 @@ func moveToRandomTile(ai *UnitAI) Action {
return nil
}
- moveTargets := ai.u.MoveRangeTiles(ai.s.Map)
- if len(moveTargets) == 0 {
+ a := NewMoveAction(ai.u)
+ options := a.Targets().NextOptions()
+ if len(options) == 0 {
return nil
}
idx := 0
- if len(moveTargets) > 1 {
- idx = ai.s.Rand.Intn(len(moveTargets) - 1)
+ if len(options) > 1 {
+ idx = ai.s.Rand.Intn(len(options) - 1)
}
- moveTarget := moveTargets[idx]
- a := NewMoveActionPrototype(ai.u).Specialize(ai.s, moveTarget)
+ a.Targets().AddTarget(options[idx])
+
return a
}
@@ -191,22 +192,28 @@ func moveTowardsNearestEnemyUnit(ai *UnitAI) Action {
break
}
}
- a := NewMoveActionPrototype(ai.u).Specialize(ai.s, ai.s.Map.TileAt(target))
+
+ a := NewMoveAction(ai.u)
+ a.Targets().AddTarget(ai.s.Map.TileAt(target))
return a
}
func attackAttackableEnemyPerm(ai *UnitAI) Action {
- attackablePerms := ai.u.AttackableEnemyPermanents(ai.s.Map)
- if ai.u.AvailAttackActions == 0 || len(attackablePerms) == 0 {
+ if ai.u.AvailAttackActions == 0 {
+ return nil
+ }
+
+ a := NewAttackAction(ai.u)
+ options := a.Targets().NextOptions()
+ if len(options) == 0 {
return nil
}
idx := 0
- if len(attackablePerms) > 1 {
- idx = ai.s.Rand.Intn(len(attackablePerms) - 1)
+ if len(options) > 1 {
+ idx = ai.s.Rand.Intn(len(options) - 1)
}
- attackablePerm := attackablePerms[idx]
- a := NewAttackActionPrototype(ai.u).Specialize(ai.s, attackablePerm)
+ a.Targets().AddTarget(options[idx])
return a
}
@@ -263,12 +270,18 @@ out:
}
}
if !inEnemyAttackRange && ai.u.HasFullAction() {
- fullActions := ai.u.Card().Impl.fullActionPrototypes(ai.u)
+ fullActions := ai.u.Card().Impl.fullActions(ai.u)
idx := 0
if len(fullActions) > 1 {
idx = ai.s.Rand.Intn(len(fullActions) - 1)
}
- a := fullActions[idx].Specialize(ai.s, ai.u)
+
+ a := fullActions[idx]
+ options := a.Targets().NextOptions()
+ if len(options) > 1 {
+ idx = ai.s.Rand.Intn(len(options) - 1)
+ }
+ a.Targets().AddTarget(options[idx])
ai.actions <- a
}
diff --git a/go/game/artifact.go b/go/game/artifact.go
index 23b653a6..acbaeb74 100644
--- a/go/game/artifact.go
+++ b/go/game/artifact.go
@@ -46,6 +46,6 @@ func (a *Artifact) Attackable() bool {
return a.Solid > 0
}
-func (a *Artifact) AvailActionPrototypes() []ActionPrototype {
- return []ActionPrototype{}
+func (a *Artifact) AvailActions() []Action {
+ return []Action{}
}
diff --git a/go/game/card.go b/go/game/card.go
index d4515676..9013a059 100644
--- a/go/game/card.go
+++ b/go/game/card.go
@@ -73,8 +73,8 @@ var CardTypeNames = map[string]CardType{
type cardImplementation interface {
spawnTiles(*State, *Player) []*Tile
- fullActionPrototypes(*Unit) []*FullActionPrototype
- freeActionPrototypes(Permanent) []*FreeActionPrototype
+ fullActions(*Unit) []*FullAction
+ freeActions(Permanent) []*FreeAction
}
// Default implementation of the cardImplementation interface.
@@ -87,11 +87,11 @@ func (impl *cardImplementationBase) spawnTiles(*State, *Player) []*Tile {
return nil
}
-func (impl *cardImplementationBase) fullActionPrototypes(*Unit) []*FullActionPrototype {
+func (impl *cardImplementationBase) fullActions(*Unit) []*FullAction {
return nil
}
-func (impl *cardImplementationBase) freeActionPrototypes(Permanent) []*FreeActionPrototype {
+func (impl *cardImplementationBase) freeActions(Permanent) []*FreeAction {
return nil
}
diff --git a/go/game/cardImplementations.go b/go/game/cardImplementations.go
index d1f8de70..8024e4fc 100644
--- a/go/game/cardImplementations.go
+++ b/go/game/cardImplementations.go
@@ -8,8 +8,9 @@ type advisorImplementation struct {
cardImplementationBase
}
-func (impl *advisorImplementation) fullActionPrototypes(u *Unit) []*FullActionPrototype {
- resolvePrototype := func(s *State, u *Unit) func(*State) {
+func (impl *advisorImplementation) fullAction(u *Unit) []*FullAction {
+ resolvePrototype := func(a Action) ActionResolveFunc {
+ u := a.Source().(*Unit)
return func(s *State) {
controller := u.Controller()
controller.DrawN(1)
@@ -19,12 +20,36 @@ func (impl *advisorImplementation) fullActionPrototypes(u *Unit) []*FullActionPr
}
}
}
- return []*FullActionPrototype{NewFullActionPrototype(u, resolvePrototype)}
+ return []*FullAction{NewFullAction(u, resolvePrototype, "draw 1. exile 1")}
+}
+
+type missionaryImplementation struct {
+ cardImplementationBase
+}
+
+func (impl *missionaryImplementation) fullAction(u *Unit) []*FullAction {
+ resolvePrototype := func(a Action) ActionResolveFunc {
+ u := a.Source().(*Unit)
+ target := a.Targets().sel[0].(*Unit)
+ return func(s *State) {
+ faith, found := target.States[UnitMarks.Faith]
+ if !found {
+ target.States[UnitMarks.Faith] = 2
+ } else {
+ target.States[UnitMarks.Faith] = faith + 2
+ if faith+2 > target.Card().BuyCost {
+ target.controller = u.Controller()
+ }
+ }
+ }
+ }
+ return []*FullAction{NewFullAction(u, resolvePrototype, "put faith ...")}
}
func init() {
cardImplementations = map[string]cardImplementation{
- "base/advisor": &advisorImplementation{},
+ "base/advisor": &advisorImplementation{},
+ "base/missionary": &missionaryImplementation{},
"misc/farmer": &farmerImplementation{},
}
diff --git a/go/game/cardParsing.go b/go/game/cardParsing.go
index 3a316c4f..afdefe54 100644
--- a/go/game/cardParsing.go
+++ b/go/game/cardParsing.go
@@ -8,10 +8,10 @@ import (
)
type parsedCardImplementation struct {
- card *Card
- spawnTilesFunc func(*State, *Player) []*Tile
- genFullActionPrototypes func(*Unit) []*FullActionPrototype
- genFreeActionPrototypes func(Permanent) []*FreeActionPrototype
+ card *Card
+ spawnTilesFunc func(*State, *Player) []*Tile
+ genFullActions func(*Unit) []*FullAction
+ genFreeActions func(Permanent) []*FreeAction
}
func (impl *parsedCardImplementation) spawnTiles(s *State, p *Player) []*Tile {
@@ -21,16 +21,16 @@ func (impl *parsedCardImplementation) spawnTiles(s *State, p *Player) []*Tile {
return nil
}
-func (impl *parsedCardImplementation) fullActionPrototypes(u *Unit) []*FullActionPrototype {
- if impl.genFullActionPrototypes != nil {
- return impl.genFullActionPrototypes(u)
+func (impl *parsedCardImplementation) fullActions(u *Unit) []*FullAction {
+ if impl.genFullActions != nil {
+ return impl.genFullActions(u)
}
return nil
}
-func (impl *parsedCardImplementation) freeActionPrototypes(p Permanent) []*FreeActionPrototype {
- if impl.genFreeActionPrototypes != nil {
- return impl.genFreeActionPrototypes(p)
+func (impl *parsedCardImplementation) freeActions(p Permanent) []*FreeAction {
+ if impl.genFreeActions != nil {
+ return impl.genFreeActions(p)
}
return nil
}
@@ -55,7 +55,7 @@ func (impl *parsedCardImplementation) parseEffect(effect string) {
}
func (impl *parsedCardImplementation) parseFullAction(fullAction string) {
- var resolveProtos []func(*State, *Unit) func(*State)
+ var resolveProtos []ActionFuncPrototype
gainRessourceRegex := regexp.MustCompile(`gain [1-9][0-9]* resource`)
if find := gainRessourceRegex.Find([]byte(fullAction)); find != nil {
@@ -65,7 +65,8 @@ func (impl *parsedCardImplementation) parseFullAction(fullAction string) {
log.Fatalf("Failed to parse resource gain full action: %s", err)
}
- resolveProto := func(_ *State, u *Unit) func(*State) {
+ resolveProto := func(a Action) ActionResolveFunc {
+ u := a.Source().(*Unit)
return func(*State) {
u.controller.gainResource(gain)
}
@@ -74,14 +75,14 @@ func (impl *parsedCardImplementation) parseFullAction(fullAction string) {
}
if resolveProtos != nil {
- impl.genFullActionPrototypes = func(u *Unit) []*FullActionPrototype {
- fullActionPrototypes := make([]*FullActionPrototype, 0, len(resolveProtos))
+ impl.genFullActions = func(u *Unit) []*FullAction {
+ fullActions := make([]*FullAction, 0, len(resolveProtos))
for _, proto := range resolveProtos {
- fullActionPrototypes = append(fullActionPrototypes, NewFullActionPrototype(u, proto))
+ fullActions = append(fullActions, NewFullAction(u, proto, ""))
}
- return fullActionPrototypes
+ return fullActions
}
}
}
diff --git a/go/game/kraken.go b/go/game/kraken.go
index 6ea22876..19adfd3c 100644
--- a/go/game/kraken.go
+++ b/go/game/kraken.go
@@ -10,23 +10,21 @@ const (
KRAKEN_NAME = "Kraken"
)
-// var KrakenDecklist = `2 kraken/angry_squid
-// 2 kraken/chachalot
-// 2 kraken/clownfish
-// 2 kraken/deja_vu!
-// 2 kraken/devour_the_poor!
-// 2 kraken/dolphin
-// 2 kraken/flying_dutchmen
-// 2 kraken/frost_pylon
-// 2 kraken/gigantic_hail
-// 2 kraken/sailfish
-// 2 kraken/sold_out!
-// 2 kraken/suprise!
-// 2 kraken/tentacle_slap
-// 2 kraken/tides_change!
-// 2 kraken/unholy_cannonball`
-
-var KrakenDecklist = `2 kraken/clownfish`
+var KrakenDecklist = `2 kraken/angry_squid
+2 kraken/chachalot
+2 kraken/clownfish
+2 kraken/deja_vu!
+2 kraken/devour_the_poor!
+2 kraken/dolphin
+2 kraken/flying_dutchmen
+2 kraken/frost_pylon
+2 kraken/gigantic_hail
+2 kraken/sailfish
+2 kraken/sold_out!
+2 kraken/suprise!
+2 kraken/tentacle_slap
+2 kraken/tides_change!
+2 kraken/unholy_cannonball`
func NewKraken(id int, s *State) *Player {
kraken := NewPlayer(id, KRAKEN_NAME, NewDeck(), s)
@@ -88,6 +86,7 @@ func (ctrl *KrakenControl) krakenTurn() {
log.Fatalf("Ai turn out of sync %d != %d", ctrl.turn, kraken.Turn)
}
+turn:
for {
drawn := kraken.Deck.Draw(1)
if len(drawn) == 0 {
@@ -107,23 +106,23 @@ func (ctrl *KrakenControl) krakenTurn() {
return true
}
- // TODO: select targets
- var targets interface{}
- if c.IsPermanent() {
- spawns := s.AvailableSpawnTiles(kraken, c)
- if len(spawns) == 0 {
- log.Println("No spawn available for", c.Name)
+ a := NewPlayAction(kraken, c, costFunc)
+ for a.Targets().RequireTargets() {
+ options := a.Targets().NextOptions()
+ if len(options) == 0 {
+ log.Println("No target available for", c.Name)
kraken.DiscardPile.AddCard(c)
- continue
+ continue turn
}
idx := 0
- if len(spawns) > 1 {
- idx = s.Rand.Intn(len(spawns) - 1)
+ if len(options) > 1 {
+ idx = s.Rand.Intn(len(options) - 1)
}
- targets = spawns[idx].Position
+ a.Targets().AddTarget(options[idx])
}
- ctrl.actions <- NewPlayActionWithSpecialCosts(kraken, c, targets, costFunc)
+
+ ctrl.actions <- a
ctrl.awaitGameStateSync()
}
diff --git a/go/game/permanent.go b/go/game/permanent.go
index 21655d71..b4090592 100644
--- a/go/game/permanent.go
+++ b/go/game/permanent.go
@@ -25,7 +25,8 @@ type Permanent interface {
AddDamage(damage int)
IsDestroyed() bool
Attackable() bool
- AvailActionPrototypes() []ActionPrototype
+ AvailActions() []Action
+ IsAvailableTile(*Tile) bool
}
type PermanentBase struct {
@@ -124,3 +125,7 @@ func (p *PermanentBase) XEffect(effect string) (int, error) {
return x, err
}
+
+func (p *PermanentBase) IsAvailableTile(tile *Tile) bool {
+ return tile.IsAvailableForCard(p.card)
+}
diff --git a/go/game/state.go b/go/game/state.go
index 816fc0d0..1d4b644f 100644
--- a/go/game/state.go
+++ b/go/game/state.go
@@ -102,11 +102,10 @@ func (s *State) IsValidPlay(a *PlayAction) error {
}
if a.Card.IsPermanent() {
- pos := a.Targets.(Position)
- spawn := s.Map.TileAt(pos)
- if !slices.Contains(s.AvailableSpawnTiles(a.Source.(*Player), a.Card), spawn) {
+ spawn := a.Targets().sel[0].(*Tile)
+ if !slices.Contains(s.AvailableSpawnTiles(a.Source().(*Player), a.Card), spawn) {
return fmt.Errorf("Spawn %v is not a valid spawn tile for %s",
- pos.String(), a.Card.Name)
+ spawn.Position.String(), a.Card.Name)
}
}
@@ -118,8 +117,8 @@ func (s *State) IsValidMove(a *MoveAction) error {
return fmt.Errorf("Move actions can only be declared during one's actions phase")
}
- u := a.Source.(*Unit)
- tile := a.Targets.(*Tile)
+ u := a.Source().(*Unit)
+ tile := a.Targets().sel[0].(*Tile)
if !slices.Contains(u.MoveRangeTiles(s.Map), tile) {
return fmt.Errorf("Unit %s at %s can not move to %s",
@@ -139,16 +138,12 @@ func (s *State) IsValidAttack(a *AttackAction) error {
return fmt.Errorf("Attack actions can only be declared during one's actions phase")
}
- unit := a.Source.(Unit)
+ unit := a.Source().(Unit)
if unit.Attack == INVALID_ATTACK() {
return fmt.Errorf("%s can not attack", unit.card.Name)
}
- target := a.Targets.(Permanent)
- if !target.Attackable() {
- return fmt.Errorf("Target %s is not attackable", target.Tile().Position.String())
- }
-
+ target := a.Targets().sel[0].(Permanent)
if !IsPositionInRange(unit.tile.Position, target.Tile().Position, unit.Attack.Range) {
return fmt.Errorf("Attack target on %s is not in attack range %d of %s on %s",
target.Tile().Position.String(), unit.Attack.Range, unit.card.Name,
@@ -158,7 +153,7 @@ func (s *State) IsValidAttack(a *AttackAction) error {
return nil
}
-func (s *State) IsValidBuy(a *BuyAction) error {
+func (s *State) isValidBuy(a *BuyAction) error {
if s.Map.HasStores() {
var storeTiles []*Tile
for pos, store := range s.Map.Stores {
@@ -189,8 +184,12 @@ func (s *State) IsValidBuy(a *BuyAction) error {
return nil
}
-func (s *State) ValidateAction(a Action) error {
- var err error = nil
+func (s *State) ValidateAction(a Action) (err error) {
+ err = a.CheckTargets(s)
+ if err != nil {
+ return
+ }
+
switch a := a.(type) {
case *PlayAction:
err = s.IsValidPlay(a)
@@ -199,9 +198,9 @@ func (s *State) ValidateAction(a Action) error {
case *AttackAction:
err = s.IsValidAttack(a)
case *BuyAction:
- err = s.IsValidBuy(a)
}
- return err
+
+ return
}
func (s *State) PushAction(a Action) {
@@ -214,18 +213,23 @@ func (s *State) PushAction(a Action) {
func (s *State) DeclareAction(a Action) {
var err error = nil
- switch a := a.(type) {
- case *BuyAction:
- if s.ActivePhase != Phases.BuyPhase {
- err = fmt.Errorf("Cards can only be bought during one's buy phase")
- }
- if !a.card.IsBuyable() {
- err = fmt.Errorf("Card %s is not buyable", a.card.Name)
- }
- case *FreeAction:
- default:
- if !s.Stack.IsEmpty() {
- err = fmt.Errorf("Slow action can not be declared while other actions are on the stack")
+
+ err = a.CheckTargets(s)
+
+ if err != nil {
+ switch a := a.(type) {
+ case *BuyAction:
+ if s.ActivePhase != Phases.BuyPhase {
+ err = fmt.Errorf("Cards can only be bought during one's buy phase")
+ }
+ if !a.card.IsBuyable() {
+ err = fmt.Errorf("Card %s is not buyable", a.card.Name)
+ }
+ case *FreeAction:
+ default:
+ if !s.Stack.IsEmpty() {
+ err = fmt.Errorf("Slow action can not be declared while other actions are on the stack")
+ }
}
}
@@ -367,12 +371,14 @@ func (s *State) EnemyUnits(player *Player) []*Unit {
})
}
-func (s *State) ResolvePlay(p *Player, c *Card, targets interface{}) {
+func (s *State) resolvePlay(p *Player, c *Card, targets *Targets) {
switch c.Type {
case CardTypes.Unit:
- s.AddNewUnit(c, targets.(Position), p)
+ tile := targets.sel[0].(*Tile)
+ s.AddNewUnit(c, tile.Position, p)
case CardTypes.Artifact:
- s.AddNewArtifact(c, targets.(Position), p)
+ tile := targets.sel[0].(*Tile)
+ s.AddNewArtifact(c, tile.Position, p)
default:
log.Printf("Resolving unhandled card type: %s\n", c.Type.String())
}
diff --git a/go/game/targets.go b/go/game/targets.go
new file mode 100644
index 00000000..1a8c7ab3
--- /dev/null
+++ b/go/game/targets.go
@@ -0,0 +1,307 @@
+package game
+
+import (
+ "fmt"
+ "log"
+ "strconv"
+ "strings"
+
+ "golang.org/x/exp/slices"
+ "muhq.space/muhqs-game/go/utils"
+)
+
+type (
+ TargetConstrainFunc func(interface{}) error
+ TargetConstrain struct {
+ desc string
+ constrain TargetConstrainFunc
+ }
+)
+
+type Targets struct {
+ s *State
+ action Action
+ constrains []*TargetConstrain
+ sel []interface{}
+}
+
+func fmtSel(sel interface{}) string {
+ switch t := sel.(type) {
+ case *Tile:
+ return t.Position.String()
+ default:
+ return fmt.Sprintf("%d", t)
+ }
+}
+
+func (t *Targets) String() string {
+ if len(t.sel) == 0 {
+ return "undecided targets"
+ }
+
+ if len(t.sel) == 1 {
+ return fmt.Sprintf("%s", fmtSel(t.sel[0]))
+ }
+
+ s := "["
+ for _, sel := range t.sel {
+ s += fmt.Sprintf("%s ", fmtSel(sel))
+ }
+ return s[:len(s)-1] + "]"
+}
+
+func newTargetsWithSel(s *State, targets []string, action Action, sel []interface{}) *Targets {
+ t := &Targets{
+ s: s,
+ sel: sel,
+ }
+
+ t.buildConstrains(targets, action)
+ return t
+}
+
+func newTargets(s *State, targets []string, action Action) *Targets {
+ return newTargetsWithSel(s, targets, action, make([]interface{}, 0, len(targets)))
+}
+
+func (t *Targets) buildConstrains(targets []string, action Action) {
+ for _, desc := range targets {
+ c := &TargetConstrain{desc, targetConstrain(desc, t.s, action)}
+ t.constrains = append(t.constrains, c)
+ }
+}
+
+func (t *Targets) RequireTargets() bool {
+ return len(t.constrains) != len(t.sel)
+}
+
+func (t *Targets) Options() [][]interface{} {
+ options := make([][]interface{}, 0, len(t.constrains))
+ for _, c := range t.constrains {
+ options = append(options, c.options(t.s))
+ }
+ return options
+}
+
+func (t *Targets) NextOptions() []interface{} {
+ next := len(t.sel)
+ return t.constrains[next].options(t.s)
+}
+
+func (t *Targets) AddTarget(target interface{}) {
+ t.sel = append(t.sel, target)
+}
+
+func (t *Targets) CheckTargets(s *State) error {
+ if t.RequireTargets() {
+ return fmt.Errorf("Number of selected targets and required ones does not match")
+ }
+
+ for i, c := range t.constrains {
+ sel := t.sel[i]
+ if err := c.constrain(sel); err != nil {
+ return fmt.Errorf("selected target %s does not fit its desciption %s: %s",
+ sel, c.desc, err)
+ }
+ }
+
+ return nil
+}
+
+func targetConstrain(desc string, s *State, action Action) TargetConstrainFunc {
+ constrains := []TargetConstrainFunc{}
+ if strings.Contains(desc, "unit") {
+ constrains = append(constrains, parseUnitTargetConstrain(desc, s, action)...)
+ } else if strings.Contains(desc, "artifact") {
+ constrains = append(constrains, parseArtifactTargetConstrain(desc, s, action)...)
+ } else if strings.Contains(desc, "tile") {
+ constrains = append(constrains, parseTileTargetConstrain(desc, s, action)...)
+ // } else if strings.Contains("action") {
+ // contrains = append(contrains, parseActionTargetConstrain(desc, s, action)...)
+ // } else if strings.Contains("spell") {
+ // contrains = append(contrains, parseSpellTargetConstrain(desc, s, action)...)
+ }
+
+ return func(t interface{}) error {
+ for _, constrain := range constrains {
+ if err := constrain(t); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }
+}
+
+func enemyPermanentTargetConstrain(action Action) TargetConstrainFunc {
+ var sourceController *Player
+ switch source := action.Source().(type) {
+ case *Player:
+ sourceController = source
+ case Permanent:
+ sourceController = source.Controller()
+ default:
+ log.Fatalf("Unhandled source type %T in enemyPermanentTargetConstrain", source)
+ }
+
+ return func(t interface{}) (err error) {
+ p, _ := t.(Permanent)
+ if !p.Controller().IsEnemy(sourceController) {
+ err = fmt.Errorf("Controller %s of target %t is not an enemy of %s",
+ p.Controller(), t, sourceController)
+ }
+ return
+ }
+}
+
+func rangeTargetConstrain(source interface{}, r int) TargetConstrainFunc {
+ var sourcePos Position
+ switch source := source.(type) {
+ case *Tile:
+ sourcePos = source.Position
+ case Permanent:
+ sourcePos = source.Tile().Position
+ default:
+ log.Fatalf("Unhandled source type %T in rangeTargetConstrain", source)
+ }
+
+ return func(t interface{}) (err error) {
+ p, _ := t.(Permanent)
+ if !IsPositionInRange(sourcePos, p.Tile().Position, r) {
+ err = fmt.Errorf("Position %s of target %s not in range %d of source's position %s",
+ p.Tile().Position, t, r, sourcePos)
+ }
+ return
+ }
+}
+
+var unitTargetConstrain TargetConstrainFunc = func(t interface{}) (err error) {
+ _, ok := t.(*Unit)
+ if !ok {
+ err = fmt.Errorf("target is %T not *Unit", t)
+ }
+ return
+}
+
+func parseUnitTargetConstrain(desc string, s *State, action Action) []TargetConstrainFunc {
+ constrains := []TargetConstrainFunc{unitTargetConstrain}
+
+ if strings.Contains(desc, "enemy unit") {
+ constrains = append(constrains, enemyPermanentTargetConstrain(action))
+ }
+
+ if strings.Contains(desc, " in range ") {
+ tokens := strings.SplitN(desc, " in range ", 2)
+ r, err := strconv.Atoi(tokens[1])
+ if err != nil {
+ log.Fatalf("Invalid range %s in target constrain %s", tokens[1], desc)
+ }
+ constrains = append(constrains, rangeTargetConstrain(action, r))
+ }
+
+ return constrains
+}
+
+var artifactTargetConstrain TargetConstrainFunc = func(t interface{}) (err error) {
+ _, ok := t.(*Artifact)
+ if !ok {
+ err = fmt.Errorf("target is %T not *Artifact", t)
+ }
+ return
+}
+
+func parseArtifactTargetConstrain(constrain string, s *State, action Action) []TargetConstrainFunc {
+ constrains := []TargetConstrainFunc{artifactTargetConstrain}
+ return constrains
+}
+
+var tileTargetConstrain TargetConstrainFunc = func(t interface{}) (err error) {
+ _, ok := t.(*Tile)
+ if !ok {
+ err = fmt.Errorf("target is %T not *Tile", t)
+ }
+ return
+}
+
+func parseTileTargetConstrain(desc string, s *State, action Action) []TargetConstrainFunc {
+ log.Println("parsing", desc)
+ constrains := []TargetConstrainFunc{tileTargetConstrain}
+
+ if moveAction, ok := action.(*MoveAction); ok {
+ u := moveAction.Source().(*Unit)
+ constrains = append(constrains, func(t interface{}) (err error) {
+ tile := t.(*Tile)
+ if slices.Contains(u.MoveRangeTiles(s.Map), tile) {
+ return nil
+ }
+ return fmt.Errorf("tile %s is not in %d movement range", tile)
+ })
+ }
+
+ var player *Player
+ switch a := action.(type) {
+ case *PlayAction:
+ player = a.Source().(*Player)
+ }
+
+ var card *Card
+ switch a := action.(type) {
+ case *PlayAction:
+ card = a.Card
+ case *MoveAction:
+ card = a.Card
+ }
+
+ if strings.Contains(desc, "spawn") {
+ if player == nil {
+ log.Fatalf("Not implemented spawn tile constrain for %T", action)
+ }
+
+ constrains = append(constrains, func(t interface{}) (err error) {
+ tile := t.(*Tile)
+ if slices.Contains(s.AvailableSpawnTiles(player, card), tile) {
+ return nil
+ }
+ return fmt.Errorf("tile %s is no spawn tile", tile)
+ })
+ }
+
+ if strings.Contains(desc, "available") {
+ constrains = append(constrains, func(t interface{}) (err error) {
+ tile := t.(*Tile)
+
+ if card == nil {
+ log.Fatalf("Not implemented availability tile constrain for %T", action)
+ }
+
+ if tile.IsAvailableForCard(card) {
+ return nil
+ }
+ return fmt.Errorf("tile %s is not available for %s", tile, card.Name)
+ })
+ }
+
+ return constrains
+}
+
+func (c *TargetConstrain) options(s *State) (options []interface{}) {
+ candidates := targetCandidates(c.desc, s)
+
+ for _, candidate := range candidates {
+ if err := c.constrain(candidate); err == nil {
+ options = append(options, candidate)
+ }
+ }
+ return
+}
+
+func targetCandidates(desc string, s *State) []interface{} {
+ if strings.Contains(desc, "unit") || strings.Contains(desc, "artifact") {
+ return utils.TypedSliceToInterfaceSlice(s.Permanents)
+ } else if strings.Contains(desc, "tile") {
+ return utils.TypedSliceToInterfaceSlice(s.Map.AllTiles())
+ }
+
+ log.Fatalf("Unimplemented target options for %s", desc)
+ return nil
+}
diff --git a/go/game/tile.go b/go/game/tile.go
index 06af795c..260747d1 100644
--- a/go/game/tile.go
+++ b/go/game/tile.go
@@ -1,6 +1,7 @@
package game
import (
+ "fmt"
"strings"
)
@@ -78,6 +79,10 @@ type Tile struct {
Raw string
}
+func (t *Tile) String() string {
+ return fmt.Sprintf("%d@%d", t.Type, t.Position.String())
+}
+
func INVALID_TILE() Tile {
return Tile{INVALID_POSITION(), nil, 0, false, ""}
}
diff --git a/go/game/unit.go b/go/game/unit.go
index f4f7d9cc..2bc90992 100644
--- a/go/game/unit.go
+++ b/go/game/unit.go
@@ -1,24 +1,65 @@
package game
-type UnitState int
+import (
+ "log"
+)
+
+type UnitMark int
const (
- Paralysis UnitState = iota + 1
- Poison
- Panic
- Rage
- Ward
+ paralysis UnitMark = iota + 1
+ poison
+ panic_
+ rage
+ ward
+ faith
)
+func (mark UnitMark) String() string {
+ switch mark {
+ case paralysis:
+ return "paralysis"
+ case poison:
+ return "poison"
+ case panic_:
+ return "panic"
+ case rage:
+ return "rage"
+ case ward:
+ return "ward"
+ case faith:
+ return "faith"
+ default:
+ log.Fatalf("Unhandled unit mark %d", mark)
+ return ""
+ }
+}
+
+var UnitMarks = struct {
+ Paralysis UnitMark
+ Poison UnitMark
+ Panic UnitMark
+ Rage UnitMark
+ Ward UnitMark
+ Faith UnitMark
+}{
+ Paralysis: paralysis,
+ Poison: poison,
+ Panic: panic_,
+ Rage: rage,
+ Ward: ward,
+ Faith: faith,
+}
+
type Unit struct {
PermanentBase
Health int
Movement Movement
Attack Attack
Upkeep int
- FullActions []*FullActionPrototype
- FreeActions []*FreeActionPrototype
- States []UnitState
+ FullActions []*FullAction
+ FreeActions []*FreeAction
+ States map[UnitMark]int
// Effects []Effects
AvailMoveActions int
AvailAttackActions int
@@ -52,12 +93,13 @@ func NewUnit(card *Card, tile *Tile, owner *Player) *Unit {
Movement: movement,
Attack: attack,
Upkeep: upkeep,
+ States: make(map[UnitMark]int),
AvailMoveActions: 1,
AvailAttackActions: 1,
}
- u.FullActions = card.Impl.fullActionPrototypes(u)
- u.FreeActions = card.Impl.freeActionPrototypes(u)
+ u.FullActions = card.Impl.fullActions(u)
+ u.FreeActions = card.Impl.freeActions(u)
tile.Permanent = u
return u
@@ -124,10 +166,6 @@ func (u *Unit) AttackableEnemyPermanents(m *Map) []Permanent {
return permanents
}
-func (u *Unit) IsAvailableTile(tile *Tile) bool {
- return tile.IsAvailableForCard(u.card)
-}
-
func (u *Unit) Fight(p Permanent) {
DealDamage(u, p, u.Attack.Damage)
}
@@ -147,26 +185,27 @@ func (u *Unit) HasFullAction() bool {
func (u *Unit) FullAction() {
}
-func (u *Unit) AvailActionPrototypes() []ActionPrototype {
- prototypes := []ActionPrototype{}
+func (u *Unit) AvailActions() (actions []Action) {
if u.AvailMoveActions > 0 && u.Movement != INVALID_MOVEMENT() {
- prototypes = append(prototypes, NewMoveActionPrototype(u))
+ actions = append(actions, NewMoveAction(u))
}
m := u.Controller().gameState.Map
if u.AvailAttackActions > 0 && u.Attack != INVALID_ATTACK() &&
len(u.AttackableEnemyPermanents(m)) > 0 {
- prototypes = append(prototypes, NewAttackActionPrototype(u))
+ actions = append(actions, NewAttackAction(u))
}
- for _, p := range u.FullActions {
- prototypes = append(prototypes, p)
+ if u.AvailAttackActions > 0 && u.AvailMoveActions > 0 {
+ for _, p := range u.FullActions {
+ actions = append(actions, p)
+ }
}
for _, p := range u.FreeActions {
- prototypes = append(prototypes, p)
+ actions = append(actions, p)
}
- return prototypes
+ return actions
}