diff options
Diffstat (limited to 'go/game/targets.go')
| -rw-r--r-- | go/game/targets.go | 307 |
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 +} |
