package game import ( "fmt" "log" ) type ( ActionCostFunc func(*State) bool ActionResolveFunc func(*State) ActionFuncPrototype func(Action) ActionResolveFunc ) type Action interface { Source() interface{} Controller() *Player Targets() *Targets Target() *Target // Shortcut for to select the first target NeedsActionCostChoice() bool CheckTargets(*State) error PayCosts(s *State) bool resolve(s *State) String() string } type PassPriority struct{ player *Player } func (a *PassPriority) Source() interface{} { return a.player } func (a *PassPriority) Controller() *Player { return a.player } func (*PassPriority) Targets() *Targets { return nil } func (*PassPriority) Target() *Target { return nil } func (*PassPriority) NeedsActionCostChoice() bool { return false } 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 TargetSelection struct { player *Player targets *Targets } func (sel *TargetSelection) Source() interface{} { return sel.player } func (sel *TargetSelection) Controller() *Player { return sel.player } func (sel *TargetSelection) Targets() *Targets { return sel.targets } func (sel *TargetSelection) Target() *Target { return sel.targets.ts[0] } func (*TargetSelection) NeedsActionCostChoice() bool { return false } func (sel *TargetSelection) CheckTargets(s *State) error { return sel.targets.CheckTargets(s) } func (*TargetSelection) PayCosts(*State) bool { return true } func (*TargetSelection) resolve(*State) {} func (*TargetSelection) String() string { return "target selection" } func newTargetSelection(player *Player, targets *Targets) Action { return &TargetSelection{player, targets} } func newHandCardSelection(p *Player, min, max int) Action { a := &TargetSelection{player: p} targetDesc := TargetDesc{"hand card", fmt.Sprintf("%d-%d", min, max)} a.targets = newTargets(newTarget(p.gameState, targetDesc, a)) return a } type ActionBase struct { source interface{} Card *Card targets *Targets resolveFunc ActionResolveFunc costFunc ActionCostFunc } func (a *ActionBase) Source() interface{} { return a.source } func (a *ActionBase) Controller() *Player { switch t := a.source.(type) { case *Player: return t case Permanent: return t.Controller() default: log.Panicf("Unhandled source type %v", t) return nil } return nil } func (a *ActionBase) resolve(s *State) { a.resolveFunc(s) } func (a *ActionBase) PayCosts(s *State) bool { if a.costFunc != nil { return a.costFunc(s) } return true } func (a *ActionBase) Targets() *Targets { return a.targets } func (a *ActionBase) Target() *Target { return a.targets.ts[0] } func (a *ActionBase) CheckTargets(s *State) error { if a.targets == nil { return nil } return a.targets.CheckTargets(s) } type TriggeredAction struct { ActionBase event Event } func newTriggeredAction(event Event, source Trigger, resolveFunc ActionResolveFunc, costFunc ActionCostFunc, ) *TriggeredAction { return &TriggeredAction{ ActionBase: ActionBase{ source: source, Card: source.Card(), resolveFunc: resolveFunc, costFunc: costFunc, }, event: event, } } func (a *TriggeredAction) String() string { return fmt.Sprintf("%v ! %v", a.event, a.source) } // Wrapper action to pass all triggered actions through the PlayerControl type DeclareTriggeredActionsAction struct { actions []*TriggeredAction } func (sel *DeclareTriggeredActionsAction) Source() interface{} { return nil } func (sel *DeclareTriggeredActionsAction) Controller() *Player { return nil } func (sel *DeclareTriggeredActionsAction) Targets() *Targets { return nil } func (sel *DeclareTriggeredActionsAction) Target() *Target { return nil } func (*DeclareTriggeredActionsAction) NeedsActionCostChoice() bool { return false } func (sel *DeclareTriggeredActionsAction) CheckTargets(s *State) error { return nil } func (*DeclareTriggeredActionsAction) PayCosts(*State) bool { return true } func (*DeclareTriggeredActionsAction) resolve(*State) {} func (*DeclareTriggeredActionsAction) String() string { return "declared triggered actions" } 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 } var ( permanentPlayActionTarget = TargetDesc{"available spawn tile", "1"} equipmentPlayActionTarget = TargetDesc{"available spawn tile", "?"} ) 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 } } 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() { if c.Type == CardTypes.Equipment { a.targets = newTargets(newTarget(s, equipmentPlayActionTarget, a)) } else { a.targets = newTargets(newTarget(s, permanentPlayActionTarget, a)) } } else if targetDesc := c.Impl.playTargets(); targetDesc != INVALID_TARGET_DESC { a.targets = newTargets(newTarget(s, targetDesc, a)) } else { a.targets = newTargets() } a.resolveFunc = func(s *State) { s.resolvePlay(p, c, a.targets) } return a } func (a *PlayAction) String() string { p := a.source.(*Player) if a.targets.RequireSelection() { return fmt.Sprintf(" %s play %s", p.Name, a.Card.Name) } if a.Card.Type == CardTypes.Equipment && len(a.Target().sel) == 0 { return fmt.Sprintf("%s play %s@next unit", p.Name, a.Card.Name) } return fmt.Sprintf("%s play %s@%v", p.Name, a.Card.Name, a.targets) } type MoveAction struct { ActionBase } var moveActionTargetDesc = TargetDesc{"available tile", "1"} func targetTile(t *Target) *Tile { if tile, ok := t.sel[0].(*Tile); ok { return tile } return t.sel[0].(*Unit).Tile() } func NewMoveAction(u *Unit) *MoveAction { a := &MoveAction{ ActionBase{ source: u, Card: u.Card(), }, } a.targets = newTargets(newTarget(u.Controller().gameState, moveActionTargetDesc, a)) a.resolveFunc = func(s *State) { t := targetTile(a.Target()) movePermanent(u, t) } a.costFunc = genMoveActionCost(u) return a } func (a *MoveAction) String() string { u := a.source.(*Unit) if a.targets.RequireSelection() { return fmt.Sprintf("move %s", u.Movement.String()) } t := targetTile(a.Target()) return fmt.Sprintf("%v -> %v", u, 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 EquipAction struct { ActionBase equipment *Equipment } func NewEquipAction(u *Unit, e *Equipment) *EquipAction { a := &EquipAction{ ActionBase{ source: u, Card: u.Card(), }, e, } a.resolveFunc = func(s *State) { u.equip(e) } return a } func (a *EquipAction) String() string { u := a.source.(*Unit) return fmt.Sprintf("%v equip %v", u, a.equipment) } type AttackAction struct { ActionBase } var attackActionTargetDesc = TargetDesc{"attackable enemy permanent", "1"} func NewAttackAction(u *Unit) *AttackAction { a := &AttackAction{ActionBase{ source: u, Card: u.Card(), }} a.targets = newTargets(newTarget(u.Controller().gameState, attackActionTargetDesc, a)) a.resolveFunc = func(s *State) { s.fight(u, a.Target().sel[0].(Permanent)) } a.costFunc = genAttackActionCost(u) return a } func (a *AttackAction) String() string { u := a.source.(*Unit) if a.targets.RequireSelection() { return fmt.Sprintf("attack %v", u.Attack) } target := a.Target().sel[0].(Permanent) return fmt.Sprintf("%v x %v", u, 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("%v <-> %v", a.Source().(*Unit), a.Artifact) } type ArtifactMoveAction struct { ActionBase Artifact Permanent } func newArtifactMoveAction(u *Unit, artifact Permanent) *ArtifactMoveAction { a := &ArtifactMoveAction{ ActionBase{ source: u, Card: u.Card(), }, artifact, } a.targets = newArtifactMoveTargets(u.Controller().gameState, a) a.resolveFunc = func(s *State) { tile := targetTile(a.Targets().ts[0]) movePermanent(u, tile) tile = targetTile(a.Targets().ts[1]) movePermanent(artifact, tile) } a.costFunc = genFullActionCost(u) return a } func (a *ArtifactMoveAction) String() string { if !a.Targets().HasSelections() { return fmt.Sprintf("%v move %v", a.Source().(*Unit), a.Artifact) } t1 := targetTile(a.Targets().ts[0]) t2 := targetTile(a.Targets().ts[1]) return fmt.Sprintf("%v,%v -> %v,%v", a.Source().(*Unit), a.Artifact, t1, t2) } type FullAction struct { ActionBase desc string tag string } func newFullAction(u *Unit, proto ActionFuncPrototype, desc string) *FullAction { a := &FullAction{ ActionBase: ActionBase{ source: u, Card: u.Card(), }, desc: desc, } a.resolveFunc = func(s *State) { rf := proto(a) rf(s) } a.costFunc = genFullActionCost(u) return a } func (a *FullAction) String() string { u := a.source.(*Unit) if a.targets == nil || a.targets.RequireSelection() { return fmt.Sprintf("↻ %s", a.desc) } return fmt.Sprintf("%v↻@%v", u, 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.RequireSelection() { return fmt.Sprintf("free_action %s", a.desc) } return fmt.Sprintf("%v: %s free_action@%v", p.Controller(), p.Card().Name, a.targets) } var buyActionTargetDesc = TargetDesc{"store card", "?"} type BuyAction struct{ TargetSelection } func (a *BuyAction) card() *Card { return a.Target().sel[0].(*Card) } func (a *BuyAction) CheckTargets(s *State) error { err := a.targets.CheckTargets(s) if err != nil { return err } return s.isValidBuy(a) } func (a *BuyAction) resolve(s *State) { a.player.Store.MoveCard(a.card(), a.player.DiscardPile) } func (a *BuyAction) PayCosts(*State) bool { cost := a.card().BuyCost if cost < 0 || cost > a.player.Resource { return false } a.player.Resource -= cost return true } func (a *BuyAction) String() string { if !a.Targets().HasSelections() { return fmt.Sprintf("%s buy", a.player.Name) } return fmt.Sprintf("%s buy %s", a.player.Name, a.card().Name) } func newBuyAction(p *Player) *BuyAction { a := &BuyAction{TargetSelection{player: p}} a.targets = newTargets(newTarget(p.gameState, buyActionTargetDesc, a)) return a } var upkeepActionTargetDesc = TargetDesc{"unit you controll", "*"} type UpkeepAction struct { TargetSelection } func (a *UpkeepAction) PayCosts(*State) bool { costs := 0 for _, t := range a.Target().sel { u := t.(*Unit) costs += u.UpkeepCost() } if a.player.Resource < costs { return false } a.player.Resource -= costs return true } func (a *UpkeepAction) resolve(*State) { p := a.player s := p.gameState for _, i := range a.Target().sel { u := i.(*Unit) s.destroyPermanent(u) } // Keep and pay for the rest for _, u := range s.Units { if u.Controller() != p { continue } p.Resource -= u.upkeep u.onUpkeep() } } func (a *UpkeepAction) String() string { return fmt.Sprintf("upkeep disbanding: %v", a.Target().sel) } func newUpkeepAction(p *Player) *UpkeepAction { a := &UpkeepAction{TargetSelection{player: p}} a.targets = newTargets(newTarget(p.gameState, upkeepActionTargetDesc, a)) return a } func (a *ActionBase) NeedsActionCostChoice() bool { return a.costFunc == nil } func UseMoveAction(a Action) { switch a := a.(type) { case *EquipAction: a.costFunc = genMoveActionCost(a.Source().(*Unit)) } } func UseAttackAction(a Action) { switch a := a.(type) { case *EquipAction: a.costFunc = genAttackActionCost(a.Source().(*Unit)) } }