Initial commit.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user