aboutsummaryrefslogtreecommitdiff
path: root/go/game/targets.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/game/targets.go')
-rw-r--r--go/game/targets.go307
1 files changed, 307 insertions, 0 deletions
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
+}