package game import ( "log" "muhq.space/muhqs-game/go/utils" ) func adjustMelee(p Permanent, damage int) { u, ok := p.(*Unit) if !ok { return } if !u.Attack.Valid() { u.Attack.attacks = append(u.Attack.attacks, 1) } else { if u.Attack.attacks[0]+damage == 0 { u.Attack.attacks = nil } else { u.Attack.attacks[0] += damage } } } func adjustAttack(p Permanent, damage int) { u, ok := p.(*Unit) if !ok { return } if !u.Attack.Valid() { u.Attack.attacks = append(u.Attack.attacks, damage) } else { // TODO: remove 0 attacks for i := range u.Attack.attacks { u.Attack.attacks[i] += damage } } } func adjustMovement(p Permanent, delta int) { u, ok := p.(*Unit) if !ok { return } if u.Movement == INVALID_MOVEMENT() && delta > 0 { u.Movement.Range = delta } else { u.Movement.Range += delta } } func adjustHealth(p Permanent, delta int) { if u, ok := p.(*Unit); ok { u.Health += delta } } // ====== Base Set ====== type advisorImpl struct{ cardImplementationBase } func (*advisorImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) return func(s *LocalState) { controller := u.Controller() controller.DrawN(1) cards := controller.PromptHandCardSelection(0, 1) if cards != nil { controller.Hand.MoveCard(cards[0], s.Exile()) } } } return []*FullAction{newFullAction(u, resolvePrototype, "draw 1. exile 1")} } type missionaryImpl struct{ cardImplementationBase } func (*missionaryImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { target := a.Target().sel[0].(*Unit) return func(s *LocalState) { target.adjustMarks(UnitMarks.Faith, 5) if target.Card().BuyCosts != nil && target.Marks(UnitMarks.Faith) >= target.Card().BuyCosts.Costs(s) { target.changeController(a.Controller()) } } } s := u.Controller().gameState a := newFullAction(u, resolvePrototype, "put 5 faith ...") a.targets = newTargets(newTarget(s, newTargetDesc("unit in range 3"), a)) return []*FullAction{a} } type swordImpl struct{ cardImplementationBase } func (*swordImpl) onPile(p Permanent) { adjustMelee(p, 1) } func (*swordImpl) onUnpile(p Permanent) { adjustMelee(p, -1) } type greatSwordImpl struct{ cardImplementationBase } func (*greatSwordImpl) onPile(p Permanent) { adjustMelee(p, 2) } func (*greatSwordImpl) onUnpile(p Permanent) { adjustMelee(p, -2) } type misinformationImpl struct{ cardImplementationBase } func (*misinformationImpl) onPlay(a *PlayAction) { a.GameState().Exile().AddCard(a.Card) } type pioneerImpl struct{ cardImplementationBase } func (*pioneerImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { return func(s *LocalState) { target := a.Target().sel[0] if artifact, ok := target.(*Artifact); ok { s.destroyPermanent(artifact) } else { tile := target.(*Tile) tile.Neutralize() } } } s := u.Controller().gameState a := newFullAction(u, resolvePrototype, "neutralize tile or artifact") a.targets = newTargets(newTarget(s, newTargetDesc("adjacent tile or adjacent artifact"), a)) return []*FullAction{a} } type recruiterImpl struct{ cardImplementationBase } func (*recruiterImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) return func(s *LocalState) { t := a.Target().sel[0].(*Tile) s.addNewUnit(NewCard("base/recruit"), t.Position, u.Controller()) } } s := u.Controller().gameState a := newFullAction(u, resolvePrototype, "create recruit") a.targets = newTargets(newTarget(s, newTargetDesc("adjacent tile"), a)) return []*FullAction{a} } func (*recruiterImpl) additionalSpawnsFor(p Permanent, ct CardType) []*Tile { if ct != CardTypes.Unit { return nil } s := p.Controller().gameState return TilesInRange(s.Map(), p, 1) } type shieldImpl struct{ cardImplementationBase } func (*shieldImpl) onPile(p Permanent) { adjustHealth(p, 1) } func (*shieldImpl) onUnpile(p Permanent) { adjustHealth(p, -1) } type taxCollectorImpl struct{ cardImplementationBase } func (*taxCollectorImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) return func(s *LocalState) { controller := u.Controller() controller.gainResource(2) } } a := newFullAction(u, resolvePrototype, "gain 2") return []*FullAction{a} } type towerShieldImpl struct{ cardImplementationBase } func (*towerShieldImpl) onPile(p Permanent) { adjustHealth(p, 2) } func (*towerShieldImpl) onUnpile(p Permanent) { adjustHealth(p, -2) } type wormtongueImpl struct{ cardImplementationBase } func (*wormtongueImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { return func(s *LocalState) { t := a.Target().sel[0].(*Player) t.DiscardPile.AddCard(NewCard("base/misinformation")) } } s := u.Controller().gameState a := newFullAction(u, resolvePrototype, "misinform") // TODO: implement player targeting a.targets = newTargets(newTarget(s, newTargetDesc("opponent"), a)) return []*FullAction{a} } type pikemanImpl struct{ cardImplementationBase } func (*pikemanImpl) onETB(s *LocalState, p Permanent) { p.(*Unit).Attack.flexAttack = pikeAttack() } // ====== Magic Set ====== type attackImpl struct{ cardImplementationBase } func (*attackImpl) playTargets() TargetDesc { return TargetDesc{"unit", "0-2"} } func (*attackImpl) onPlay(a *PlayAction) { s := a.GameState() sel := a.targets.Cur().sel switch len(sel) { case 1: s.addEotEffect(newAttackModificationEffect(sel[0].(*Unit), 2)) case 2: s.addEotEffect(newAttackModificationEffect(sel[0].(*Unit), 1)) s.addEotEffect(newAttackModificationEffect(sel[1].(*Unit), 1)) } } type appearImpl struct{ cardImplementationBase } func (*appearImpl) playTargets() TargetDesc { return newTargetDesc("free tile") } func (*appearImpl) onPlay(a *PlayAction) { selection := a.Controller().PromptHandCardSelection(1, 1) if len(selection) != 1 || selection[0].Type != CardTypes.Unit { return } unitCard := selection[0] if unitCard.PlayCosts.Costs(a.GameState()) > a.ChoosenVariadicCost { return } a.Controller().Hand.RemoveCard(unitCard) tile := a.targets.Cur().sel[0].(*Tile) a.GameState().addNewUnit(unitCard, tile.Position, a.Controller()) } type healImpl struct{ cardImplementationBase } func (*healImpl) playTargets() TargetDesc { return newTargetDesc("unit") } func (*healImpl) onPlay(a *PlayAction) { u := a.targets.Cur().sel[0].(*Unit) // TODO: prompt damage or poison choice u.adjustDamage(-2) } type dieImpl struct{ cardImplementationBase } func (*dieImpl) playTargets() TargetDesc { return newTargetDesc("unit") } type moreImpl struct{ cardImplementationBase } func (*moreImpl) onPlay(a *PlayAction) { a.Controller().DrawN(2) } type mineImpl struct{ cardImplementationBase } func (*mineImpl) onPlay(a *PlayAction) { stolen := 0 for _, player := range a.GameState().Players() { if a.Controller() == player || !a.Controller().IsEnemy(player) { continue } player.reduceResource(2) stolen += 2 } a.Controller().gainResource(stolen) } func (*dieImpl) onPlay(a *PlayAction) { a.GameState().destroyPermanent(a.targets.Cur().sel[0].(Permanent)) } type ritualImpl struct{ cardImplementationBase } func (*ritualImpl) onPlay(a *PlayAction) { a.Controller().gainResource(3) } type rushImpl struct{ cardImplementationBase } func (*rushImpl) playTargets() TargetDesc { return TargetDesc{"unit", "0-2"} } func (*rushImpl) onPlay(a *PlayAction) { s := a.GameState() sel := a.targets.Cur().sel switch len(sel) { case 1: s.addEotEffect(newMovementModificationEffect(sel[0].(*Unit), 2)) case 2: s.addEotEffect(newMovementModificationEffect(sel[0].(*Unit), 1)) s.addEotEffect(newMovementModificationEffect(sel[1].(*Unit), 1)) } } type selectImpl struct{ cardImplementationBase } func (*selectImpl) onPlay(a *PlayAction) { p := a.Controller() p.DrawN(1) cards := p.PromptHandCardSelection(0, 1) if cards != nil { p.Hand.MoveCard(cards[0], a.GameState().Exile()) } } type shroudImpl struct{ cardImplementationBase } func (*shroudImpl) playTargets() TargetDesc { return newTargetDesc("unit") } func (*shroudImpl) onPlay(a *PlayAction) { u := a.targets.Cur().sel[0].(*Unit) a.GameState().addEotEffect(newTmpEffect(u, "shroud")) } type switchImpl struct{ cardImplementationBase } func (*switchImpl) playTargets() TargetDesc { return TargetDesc{"permanent", "2"} } func (*switchImpl) onPlay(a *PlayAction) { perms := utils.InterfaceSliceToTypedSlice[Permanent](a.targets.Cur().sel) a.GameState().switchPermanents(perms[0], perms[1]) } type transmuteImpl struct{ cardImplementationBase } func (*transmuteImpl) playTargets() TargetDesc { return newTargetDesc("store card") } func (*transmuteImpl) onPlay(a *PlayAction) { storeCard := a.targets.Cur().sel[0].(*Card) a.Controller().Store.MoveCard(storeCard, a.Controller().Hand) a.GameState().Exile().AddCard(a.Card) } // ====== Nautics Set ====== var fishTrapAoE areaEffect = newGrantFullActionEffect("nautics/fisher", func(a Action) ActionResolveFunc { u := a.Source().(*Unit) return func(s *LocalState) { controller := u.Controller() controller.gainResource(2) } }, "gain 2 resource", "fishTrapAction") type fishTrapImpl struct { cardImplementationBase aoe areaEffect } func (*fishTrapImpl) additionalSpawnsFor(Permanent, CardType) []*Tile { return nil } func (*fishTrapImpl) stateBasedActions(*LocalState, Permanent) {} func (i *fishTrapImpl) onEntering(t *Tile) { s := t.Permanent.Controller().gameState for _, tile := range TilesInRange(s.Map(), t.Permanent, 1) { tile.addEffect(i.aoe) } } func (i *fishTrapImpl) onLeaving(t *Tile) { s := t.Permanent.Controller().gameState for _, tile := range TilesInRange(s.Map(), t.Permanent, 1) { tile.removeEffect(i.aoe) } } type fisherImpl struct{ cardImplementationBase } func (*fisherImpl) fullActions(u *Unit) []*FullAction { s := u.Controller().gameState resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) return func(s *LocalState) { t := a.Target().sel[0].(*Tile) s.addNewArtifact(NewCard("nautics/fish_trap"), t.Position, u.Controller()) } } a := newFullAction(u, resolvePrototype, "create fish trap") a.targets = newTargets(newTarget(s, newTargetDesc("adjacent free water tile"), a)) return []*FullAction{a} } type captainImpl struct{ cardImplementationBase } func (*captainImpl) onPile(containing Permanent) { // TODO: implement static effect } func (*captainImpl) onUnpile(containing Permanent) { // TODO: remove static effect } type sailorImpl struct{ cardImplementationBase } func (*sailorImpl) onPile(containing Permanent) { if u, ok := containing.(*Unit); ok && u.Movement.Swimming { u.Movement.Range += 1 } } func (*sailorImpl) onUnpile(containing Permanent) { if u, ok := containing.(*Unit); ok && u.Movement.Swimming { u.Movement.Range -= 1 } } // ====== Kraken Set ====== type clownfishImpl struct{ cardImplementationBase } func (*clownfishImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) return func(s *LocalState) { controller := u.Controller() controller.gainResource(4) } } a := newFullAction(u, resolvePrototype, "gain 4") return []*FullAction{a} } type tidesChangeImpl struct{ cardImplementationBase } func (*tidesChangeImpl) onPlay(a *PlayAction) { a.GameState().redistributeMapStoreCards() } type dejaVuImpl struct{ cardImplementationBase } func (*dejaVuImpl) onPlay(a *PlayAction) { for _, p := range a.GameState().Players() { if !p.IsKraken() { p.Resource = a.GameState().Map().ResourceGain } } } type devourThePoorImpl struct{ cardImplementationBase } func (*devourThePoorImpl) onPlay(a *PlayAction) { s := a.GameState() for _, u := range s.EnemyUnits(a.Controller()) { if !u.Card().IsBuyable() || u.Card().BuyCosts.Costs(s) < 7 { s.destroyPermanent(u) } } } type dolphinImpl struct{ cardImplementationBase } func (*dolphinImpl) fullActions(u *Unit) []*FullAction { s := u.Controller().gameState resolvePrototype := func(a Action) ActionResolveFunc { return func(s *LocalState) { t := a.Target().sel[0].(*Unit) // 1. +1 Attack // 2. +1 Armor // 3. +2 Movement // 4. +1 Action // 5. +2 Pierce // 6. All choice := s.Rand.Intn(5) + 1 switch choice { case 1: s.addEotEffect(newAttackModificationEffect(t, 1)) case 2: case 3: s.addEotEffect(newMovementModificationEffect(t, 1)) case 4: case 5: case 6: } // TODO: implement eot effects } } a := newFullAction(u, resolvePrototype, "random dolphin buff") a.targets = newTargets(newTarget(s, newTargetDesc("adjacent akkied unit"), a)) return []*FullAction{a} } type drownedSailorImpl struct{ sailorImpl } func (*drownedSailorImpl) onDrop(sailor Permanent) { sailor.Controller().gameState.destroyPermanent(sailor) } // TODO: implement hook for the containing permanent on pile to change AI type flyingDutchmenImpl struct{ cardImplementationBase } func (*flyingDutchmenImpl) onETB(s *LocalState, p Permanent) { s.addTrigger(newEnemyDeathTrigger(p, func(s *LocalState) { sailor := NewCard("kraken/drowned_sailor") if p.Tile().IsAvailableForCard(sailor) { addPermanentToPile(p, NewUnit(sailor, nil, p.Controller())) } }, p.Card().getEffects()[1])) } type frostPylonAoE struct{} func (*frostPylonAoE) onEntering(Permanent) { // TODO: prevent actions and grant new full action } func (*frostPylonAoE) onLeaving(Permanent) { // TODO: remove effect } type frostPylonImpl struct { cardImplementationBase aoe frostPylonAoE } func (*frostPylonImpl) spawnTiles(s *LocalState, kraken *Player) (spawns []*Tile) { for _, u := range s.EnemyUnits(kraken) { candiates := TilesInRange(s.Map(), u, 1) for _, candidate := range candiates { if candidate.IsFree() { spawns = append(spawns, candidate) } } } return } func (*frostPylonImpl) stateBasedActions(s *LocalState, p Permanent) { if p.Marks(PermanentMarks.Crack) >= 3 { s.destroyPermanent(p) } } func (i *frostPylonImpl) onEntering(t *Tile) { s := t.Permanent.Controller().gameState for _, tile := range TilesInRange(s.Map(), t.Permanent, 1) { tile.addEffect(&i.aoe) } } func (i *frostPylonImpl) onLeaving(t *Tile) { s := t.Permanent.Controller().gameState for _, tile := range TilesInRange(s.Map(), t.Permanent, 1) { tile.removeEffect(&i.aoe) } } type giganticHailImpl struct { cardImplementationBase } func (*giganticHailImpl) playTargets() TargetDesc { return INVALID_TARGET_DESC } func (*giganticHailImpl) onPlay(a *PlayAction) { s := a.GameState() emptyTiles := s.Map().FreeTiles() choices := len(emptyTiles) for i := 0; i < 3; i++ { if choices == 0 { return } choice := s.Rand.Intn(choices - 1) s.addNewArtifact(NewCard("kraken/ice_berg"), emptyTiles[choice].Position, a.Controller()) emptyTiles[choice] = emptyTiles[choices-1] choices-- } } type soldOutImpl struct{ cardImplementationBase } func (*soldOutImpl) onPlay(a *PlayAction) { s := a.GameState() nStores := len(s.Stores()) storePositions := make([]Position, 0, nStores) for pos := range s.Map().Stores { storePositions = append(storePositions, pos) } storePos := storePositions[s.Rand.Intn(nStores-1)] s.Map().StoreOn(storePos).MoveInto(s.Exile()) storeTile := s.Map().TileAt(storePos) storeTile.Neutralize() } type supriseImpl struct{ cardImplementationBase } func (*supriseImpl) onPlay(a *PlayAction) { a.Controller().Ctrl.(*KrakenControl).activeSuprise = true } type tentacleSlapImpl struct{ cardImplementationBase } func (*tentacleSlapImpl) playTargets() TargetDesc { return INVALID_TARGET_DESC } func (*tentacleSlapImpl) onPlay(a *PlayAction) { x := 0 for _, p := range a.GameState().Players() { if p.IsKraken() { continue } x += p.Hand.Size() p.discardHand() } gain := 5 - x if gain > 0 { a.Controller().gainResource(gain) } } type unholyCannonballImpl struct{ cardImplementationBase } func (*unholyCannonballImpl) playTargets() TargetDesc { return INVALID_TARGET_DESC } func (*unholyCannonballImpl) onPlay(a *PlayAction) { s := a.GameState() kraken := a.Controller() krakenTiles := s.Map().FilterTiles(func(t *Tile) bool { return t.Raw == "the kraken" }) if len(krakenTiles) != 1 { log.Fatal("No kraken tile found in Map") } enemiesInRange2 := []*Unit{} for _, u := range s.EnemyUnits(kraken) { if u.Tile() == nil { continue } if IsPositionInRange(u.Tile().Position, krakenTiles[0].Position, 2) { enemiesInRange2 = append(enemiesInRange2, u) break } } if len(enemiesInRange2) == 0 { kraken.gainResource(5) } else { for _, u := range enemiesInRange2 { u.adjustMarks(UnitStates.Panic, 1) } } } type illusionistImpl struct{ cardImplementationBase } func (*illusionistImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { u := a.Source().(*Unit) return func(s *LocalState) { t := a.Target().sel[0].(*Tile) s.addNewUnit(NewCard("exp1/illusion"), t.Position, u.Controller()) } } s := u.Controller().gameState a := newFullAction(u, resolvePrototype, "create illusion") a.targets = newTargets(newTarget(s, newTargetDesc("adjacent free tile"), a)) return []*FullAction{a} } type illusionImpl struct{ cardImplementationBase } func (*illusionImpl) onETB(s *LocalState, p Permanent) { s.addTrigger(newTargetedTrigger(p, true, func(s *LocalState) { s.destroyPermanent(p) }, p.Card().getEffects()[0])) } func init() { cardImplementations = map[string]cardImplementation{ "base/advisor": &advisorImpl{}, "base/missionary": &missionaryImpl{}, "base/sword": &swordImpl{}, "base/greatsword": &greatSwordImpl{}, "base/misinformation": &misinformationImpl{}, "base/pioneer": &pioneerImpl{}, "base/recruiter": &recruiterImpl{}, "base/shield": &shieldImpl{}, "base/tax_collector": &taxCollectorImpl{}, "base/tower_shield": &towerShieldImpl{}, "base/wormtongue": &wormtongueImpl{}, "base/pikeman": &pikemanImpl{}, "magic/attack!": &attackImpl{}, "magic/appear!": &appearImpl{}, "magic/die!": &dieImpl{}, "magic/heal!": &healImpl{}, "magic/more!": &moreImpl{}, "magic/mine!": &mineImpl{}, "magic/ritual!": &ritualImpl{}, "magic/rush!": &rushImpl{}, "magic/select!": &selectImpl{}, "magic/shroud!": &shroudImpl{}, "magic/switch!": &switchImpl{}, "magic/transmute!": &transmuteImpl{}, "nautics/captain": &captainImpl{}, "nautics/fish_trap": &fishTrapImpl{aoe: fishTrapAoE}, "nautics/fisher": &fisherImpl{}, "nautics/sailor": &sailorImpl{}, "kraken/clownfish": &clownfishImpl{}, "kraken/deja_vu!": &dejaVuImpl{}, "kraken/devour_the_poor!": &devourThePoorImpl{}, "kraken/dolphin": &dolphinImpl{}, "kraken/drowned_sailor": &drownedSailorImpl{}, "kraken/flying_dutchmen": &flyingDutchmenImpl{}, "kraken/frost_pylon": &frostPylonImpl{}, "kraken/gigantic_hail": &giganticHailImpl{}, "kraken/sold_out!": &soldOutImpl{}, "kraken/suprise!": &supriseImpl{}, "kraken/tentacle_slap": &tentacleSlapImpl{}, "kraken/tides_change!": &tidesChangeImpl{}, "kraken/unholy_cannonball": &unholyCannonballImpl{}, "exp1/illusionist": &illusionistImpl{}, "exp1/illusion": &illusionImpl{}, } }