package game import ( "log" "muhq.space/muhqs-game/go/utils" ) func adjustMelee(p Permanent, damage int) { u, ok := p.(*Unit) if !ok { return } if INVALID_ATTACK(u.Attack) { 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 INVALID_ATTACK(u.Attack) { 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 *State) { 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 { u := a.Source().(*Unit) target := a.Target().sel[0].(*Unit) return func(s *State) { target.adjustMarks(UnitMarks.Faith, 2) if target.Marks(UnitMarks.Faith) > target.Card().BuyCost { target.controller = u.Controller() } } } s := u.Controller().gameState a := newFullAction(u, resolvePrototype, "put faith ...") a.targets = newTargets(newTarget(s, newTargetDesc("unit"), 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(s *State, c *Card, _ *Player, _ *Targets) { s.Exile.AddCard(c) } type pioneerImpl struct{ cardImplementationBase } func (*pioneerImpl) fullActions(u *Unit) []*FullAction { resolvePrototype := func(a Action) ActionResolveFunc { return func(s *State) { 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 *State) { 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 *State) { 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 *State) { 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} } // ====== Magic Set ====== type attackImpl struct{ cardImplementationBase } func (*attackImpl) playTargets() TargetDesc { return TargetDesc{"unit", "0-2"} } func (*attackImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { sel := t.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 healImpl struct{ cardImplementationBase } func (*healImpl) playTargets() TargetDesc { return newTargetDesc("unit") } func (*healImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { u := t.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(s *State, c *Card, p *Player, t *Targets) { p.DrawN(2) } type mineImpl struct{ cardImplementationBase } func (*mineImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { stolen := 0 for _, player := range s.Players { if p == player || !p.IsEnemy(player) { continue } player.reduceResource(2) stolen += 2 } p.gainResource(stolen) } func (*dieImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { s.destroyPermanent(t.Cur().sel[0].(Permanent)) } type ritualImpl struct{ cardImplementationBase } func (*ritualImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { p.gainResource(3) } type rushImpl struct{ cardImplementationBase } func (*rushImpl) playTargets() TargetDesc { return TargetDesc{"unit", "0-2"} } func (*rushImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { sel := t.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(s *State, c *Card, p *Player, t *Targets) { p.DrawN(1) cards := p.PromptHandCardSelection(0, 1) if cards != nil { p.Hand.MoveCard(cards[0], s.Exile) } } type shroudImpl struct{ cardImplementationBase } func (*shroudImpl) playTargets() TargetDesc { return newTargetDesc("unit") } func (*shroudImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { u := t.Cur().sel[0].(*Unit) s.addEotEffect(newTmpEffect(u, "shroud")) } type switchImpl struct{ cardImplementationBase } func (*switchImpl) playTargets() TargetDesc { return TargetDesc{"permanent", "2"} } func (*switchImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { perms := utils.InterfaceSliceToTypedSlice[Permanent](t.Cur().sel) s.switchPermanents(perms[0], perms[1]) } type transmuteImpl struct{ cardImplementationBase } func (*transmuteImpl) playTargets() TargetDesc { return newTargetDesc("store card") } func (*transmuteImpl) onPlay(s *State, c *Card, p *Player, t *Targets) { storeCard := t.Cur().sel[0].(*Card) p.Store.MoveCard(storeCard, p.Hand) s.Exile.AddCard(c) } // ====== Nautics Set ====== var fishTrapAoE areaEffect = newGrantFullActionEffect("nautics/fisher", func(a Action) ActionResolveFunc { u := a.Source().(*Unit) return func(s *State) { 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(*State, 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 *State) { 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 tidesChangeImpl struct{ cardImplementationBase } func (*tidesChangeImpl) onPlay(s *State, _ *Card, _ *Player, _ *Targets) { s.redistributeMapStoreCards() } type dejaVuImpl struct{ cardImplementationBase } func (*dejaVuImpl) onPlay(s *State, _ *Card, _ *Player, _ *Targets) { for _, p := range s.Players { if !p.IsKraken() { p.Resource = s.Map.ResourceGain } } } type devourThePoorImpl struct{ cardImplementationBase } func (*devourThePoorImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { for _, u := range s.EnemyUnits(kraken) { if u.Card().BuyCost < 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 *State) { 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 death trigger // TODO: implement hook for the containing permanent on pile type flyingDutchmenImpl struct{ cardImplementation } 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 *State, kraken *Player) (spawns []*Tile) { for _, u := range s.EnemyUnits(kraken) { spawns = append(spawns, TilesInRange(s.Map, u, 1)...) } return } func (*frostPylonImpl) stateBasedActions(s *State, 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(s *State, _ *Card, kraken *Player, _ *Targets) { 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, kraken) emptyTiles[choice] = emptyTiles[choices-1] choices-- } } type soldOutImpl struct{ cardImplementationBase } func (*soldOutImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { nStores := len(s.Map.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(s *State, _ *Card, kraken *Player, _ *Targets) { kraken.Ctrl.(*KrakenControl).activeSuprise = true } type tentacleSlapImpl struct{ cardImplementationBase } func (*tentacleSlapImpl) playTargets() TargetDesc { return INVALID_TARGET_DESC } func (*tentacleSlapImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { x := 0 for _, p := range s.Players { if p.IsKraken() { continue } x += p.Hand.Size() p.discardHand() } gain := 5 - x if gain > 0 { kraken.gainResource(gain) } } type unholyCannonballImpl struct{ cardImplementationBase } func (*unholyCannonballImpl) playTargets() TargetDesc { return INVALID_TARGET_DESC } func (*unholyCannonballImpl) onPlay(s *State, _ *Card, kraken *Player, _ *Targets) { 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) } } } 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{}, "magic/attack!": &attackImpl{}, "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/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{}, } }