package game import ( "fmt" "log" "strconv" "strings" "golang.org/x/exp/slices" "muhq.space/muhqs-game/go/utils" ) type Permanent interface { Card() *Card Tile() *Tile SetTile(*Tile) Damage() int ContainingPerm() Permanent Pile() []Permanent Controller() *Player Owner() *Player Effects() []string HasEffect(string) bool XEffect(string) (int, error) Marks(PermanentMark) int adjustMarks(PermanentMark, int) IsDestroyed() bool Attackable() bool String() string CurrentlyAvailActions() []Action AvailActions() []Action IsAvailableTile(*Tile) bool fight(p Permanent) adjustDamage(damage int) addTmpEffect(tmpEffect string) removeTmpEffect(tmpEffect string) setContainingPerm(p Permanent) addPermanentToPile(p Permanent) clearPile() removePermanentFromPile(p Permanent) onPile(containing Permanent) onUnpile(containing Permanent) onDrop(containing Permanent) } type permanentBase struct { card *Card tile *Tile damage int pile []Permanent containingPerm Permanent controller *Player owner *Player tmpEffects []string marks map[PermanentMark]int } func newPermanentBase(card *Card, tile *Tile, owner *Player) permanentBase { if tile != nil && tile.Permanent != nil { log.Panicf("Tile at %v already occupied by %v", tile.Position, tile.Permanent) } return permanentBase{ card: card, tile: tile, controller: owner, owner: owner, } } func (p *permanentBase) Card() *Card { return p.card } func (p *permanentBase) Tile() *Tile { return p.tile } func (p *permanentBase) SetTile(t *Tile) { p.tile = t } func (p *permanentBase) Damage() int { return p.damage } func (p *permanentBase) ContainingPerm() Permanent { return p.containingPerm } func (p *permanentBase) Pile() []Permanent { return p.pile } func (p *permanentBase) Controller() *Player { return p.controller } func (p *permanentBase) Owner() *Player { return p.owner } func FmtPermanent(p Permanent) string { typeIndicator := strings.ToUpper(p.Card().Type.String()[:1]) containing := p.ContainingPerm() if containing == nil { return fmt.Sprintf("%s%v", typeIndicator, p.Tile().Position) } for i, piled := range containing.Pile() { if piled == p { return fmt.Sprintf("%s%v[%d]", typeIndicator, containing.Tile().Position, i+1) } } log.Panicf("Permanent piled at %v not found in the pile", TileOrContainingPermTile(p).Position) return "" } func FindPermanent(s *LocalState, desc string) Permanent { var pos Position _, err := fmt.Sscanf(desc[1:strings.Index(desc, ")")], POSITION_FMT, &pos.X, &pos.Y) if err != nil { log.Panicf("desc %q not a valid permanent description", desc) } p := s.Map().TileAt(pos).Permanent return p } func DealDamage(src, dest Permanent, damage int) { actualDamage := damage shield, err := dest.XEffect("shield") if err != nil { // TODO: implement piercing actualDamage -= shield if actualDamage < 0 { actualDamage = 0 } } dest.adjustDamage(actualDamage) } func (p *permanentBase) adjustDamage(damage int) { p.damage += damage if p.damage < 0 { p.damage = 0 } } func (p *permanentBase) fight(Permanent) {} func (p *permanentBase) addTmpEffect(tmpEffect string) { p.tmpEffects = append(p.tmpEffects, tmpEffect) } func (p *permanentBase) removeTmpEffect(tmpEffect string) { p.tmpEffects = utils.RemoveFromUnorderedSlice[string](p.tmpEffects, tmpEffect) } func (p *permanentBase) Effects() []string { return append(p.card.getEffects(), p.tmpEffects...) } func (p *permanentBase) HasEffect(effect string) bool { return slices.Contains(p.tmpEffects, effect) || p.card.hasEffect(effect) } func (p *permanentBase) XEffect(effect string) (int, error) { x, err := p.card.getXEffect(effect) for _, e := range p.tmpEffects { if !strings.Contains(e, effect) { continue } tokens := strings.SplitN(e, " ", 2) tmpX, err := strconv.Atoi(tokens[1]) if err != nil { return 0, err } x += tmpX } return x, err } func (p *permanentBase) Marks(mark PermanentMark) int { if amount, found := p.marks[mark]; found { return amount } return 0 } func (p *permanentBase) adjustMarks(mark PermanentMark, amount int) { if amount, found := p.marks[mark]; found { p.marks[mark] += amount } else { p.marks[mark] = amount } } func (p *permanentBase) CurrentlyAvailActions() []Action { return nil } func (p *permanentBase) AvailActions() []Action { return nil } func (p *permanentBase) IsAvailableTile(tile *Tile) bool { return tile.IsAvailableForCard(p.card) } func (p *permanentBase) setContainingPerm(containing Permanent) { p.containingPerm = containing } func (p *permanentBase) addPermanentToPile(a Permanent) { p.pile = append(p.pile, a) } func (p *permanentBase) clearPile() { p.pile = nil } func (p *permanentBase) removePermanentFromPile(rm Permanent) { p.pile = utils.RemoveFromUnorderedSlice[Permanent](p.pile, rm) } func (b *permanentBase) onPile(containing Permanent) { b.Card().Impl.onPile(containing) } func (b *permanentBase) onUnpile(containing Permanent) { b.Card().Impl.onUnpile(containing) } func (b *permanentBase) onDrop(dropped Permanent) { b.onUnpile(dropped.ContainingPerm()) b.Card().Impl.onDrop(dropped) } func addPermanentToPile(containing, p Permanent) { p.setContainingPerm(containing) containing.addPermanentToPile(p) p.onPile(containing) } func removePermanentFromPile(containing, rm Permanent) { containing.removePermanentFromPile(rm) rm.onUnpile(containing) rm.setContainingPerm(nil) } func movePermanent(p Permanent, t *Tile) { if !p.IsAvailableTile(t) { log.Panicf("moving %v to not available tile %v", p, t) } if p.ContainingPerm() != nil { removePermanentFromPile(p.ContainingPerm(), p) } else { leaveTile(p) } enterTileOrPile(p, t) } func enterTile(p Permanent, t *Tile) { p.SetTile(t) t.entering(p) p.Card().Impl.onEntering(t) } func enterTileOrPile(p Permanent, t *Tile) { if t.Permanent == nil { enterTile(p, t) } else { addPermanentToPile(t.Permanent, p) p.SetTile(nil) } } func _leaveTile(p Permanent) { if p.Tile() != nil { p.Card().Impl.onLeaving(p.Tile()) p.Tile().leaving(p) } } func leaveTile(p Permanent) { if p.Tile() != nil { p.Card().Impl.onLeaving(p.Tile()) p.Tile().leaving(p) p.SetTile(nil) } } func TileOrContainingPermTile(p Permanent) *Tile { tile := p.Tile() if tile == nil { tile = p.ContainingPerm().Tile() } return tile }