123 lines
3.9 KiB
Go
123 lines
3.9 KiB
Go
package game
|
|
|
|
// BattleUnit is a pet on the battle line with its attached foods applied.
|
|
// Power (attack) is unaffected by damage; a unit dies when Damage >= Power.
|
|
type BattleUnit struct {
|
|
Card Card `json:"card"`
|
|
Foods []Card `json:"foods"`
|
|
Bonus int `json:"bonus"` // total power added by foods
|
|
Damage int `json:"damage"` // damage markers accumulated this battle
|
|
}
|
|
|
|
func (u *BattleUnit) Power() int { return u.Card.Power + u.Bonus }
|
|
func (u *BattleUnit) Alive() bool { return u.Damage < u.Power() }
|
|
|
|
// BattleEvent is one step of the battle, in order, for clients to animate.
|
|
type BattleEvent struct {
|
|
Type string `json:"type"` // "clash"
|
|
// Indexes into the initial lineups (per seat) of the two front pets.
|
|
Units []int `json:"units"` // one entry per seat
|
|
// Damage each front pet has accumulated after the clash, per seat.
|
|
Damage []int `json:"damage"`
|
|
// Whether each front pet died in the clash, per seat.
|
|
Died []bool `json:"died"`
|
|
}
|
|
|
|
// BattleResult is the full, public record of one round's battle.
|
|
type BattleResult struct {
|
|
Round int `json:"round"`
|
|
Lineups [][]BattleUnit `json:"lineups"` // initial lineups per seat
|
|
WastedFood [][]Card `json:"wastedFoods"` // foods with no pet beneath them, per seat
|
|
Events []BattleEvent `json:"events"`
|
|
WinnerSeat int `json:"winnerSeat"` // -1 = draw
|
|
Trophies int `json:"trophies"` // awarded to the winner
|
|
}
|
|
|
|
// buildLineup walks a deck top-to-bottom, attaching each run of foods to the
|
|
// next pet below it. Foods after the last pet affect nothing and are wasted.
|
|
func buildLineup(deck []Card) (units []BattleUnit, wasted []Card) {
|
|
var pendingFoods []Card
|
|
for _, c := range deck {
|
|
if c.IsFood() {
|
|
pendingFoods = append(pendingFoods, c)
|
|
continue
|
|
}
|
|
u := BattleUnit{Card: c, Foods: pendingFoods}
|
|
for _, f := range pendingFoods {
|
|
if f.Food == FoodApple {
|
|
u.Bonus++
|
|
}
|
|
}
|
|
pendingFoods = nil
|
|
units = append(units, u)
|
|
}
|
|
return units, pendingFoods
|
|
}
|
|
|
|
// resolveBattle simulates the battle from the players' arranged decks,
|
|
// records the event log, awards trophies, and moves to PhaseBattle.
|
|
//
|
|
// Combat: the two front pets deal their full Power to each other
|
|
// simultaneously as damage markers. A pet with Damage >= Power dies. Since
|
|
// remaining health never exceeds Power, at least one pet dies every clash,
|
|
// so the loop always terminates (until effects say otherwise).
|
|
func (g *Game) resolveBattle() {
|
|
res := &BattleResult{
|
|
Round: g.Round,
|
|
Lineups: make([][]BattleUnit, len(g.Players)),
|
|
WastedFood: make([][]Card, len(g.Players)),
|
|
WinnerSeat: -1,
|
|
}
|
|
live := make([][]BattleUnit, len(g.Players)) // working copies
|
|
front := make([]int, len(g.Players)) // index of each seat's front pet
|
|
for _, p := range g.Players {
|
|
units, wasted := buildLineup(p.Deck)
|
|
res.Lineups[p.Seat] = units
|
|
res.WastedFood[p.Seat] = wasted
|
|
live[p.Seat] = append([]BattleUnit(nil), units...)
|
|
}
|
|
|
|
// Two-player combat. Effects and >2 player battle formats come later;
|
|
// the surrounding state (lineups, events) is already per-seat.
|
|
a, b := 0, 1
|
|
for front[a] < len(live[a]) && front[b] < len(live[b]) {
|
|
ua, ub := &live[a][front[a]], &live[b][front[b]]
|
|
ua.Damage += ub.Power()
|
|
ub.Damage += ua.Power()
|
|
ev := BattleEvent{
|
|
Type: "clash",
|
|
Units: []int{front[a], front[b]},
|
|
Damage: []int{ua.Damage, ub.Damage},
|
|
Died: []bool{!ua.Alive(), !ub.Alive()},
|
|
}
|
|
res.Events = append(res.Events, ev)
|
|
if !ua.Alive() {
|
|
front[a]++
|
|
}
|
|
if !ub.Alive() {
|
|
front[b]++
|
|
}
|
|
}
|
|
|
|
trophies := 1
|
|
if g.Round == MaxRounds {
|
|
trophies = 2
|
|
}
|
|
switch {
|
|
case front[a] < len(live[a]):
|
|
res.WinnerSeat = a
|
|
case front[b] < len(live[b]):
|
|
res.WinnerSeat = b
|
|
}
|
|
if res.WinnerSeat >= 0 {
|
|
res.Trophies = trophies
|
|
g.Players[res.WinnerSeat].Trophies += trophies
|
|
}
|
|
|
|
g.Battle = res
|
|
g.Phase = PhaseBattle
|
|
for _, p := range g.Players {
|
|
p.Ready = false
|
|
}
|
|
}
|