Fix false draw case.
This commit is contained in:
@@ -116,9 +116,10 @@ Six rounds, each with its own shop tier deck. Per round:
|
||||
(Peacock, Camel, Gorilla). Shields (Turtle/Gorilla/Melon) block whole
|
||||
hits, Garlic shaves 1 per attack, and Scorpion KOs anything its clash
|
||||
attack manages to hurt. Hippo heals when enemies faint; Rhino rocks each
|
||||
enemy pet as it's played. A clash that changes nothing ends the battle
|
||||
as a stalemate draw. Last player able to field a pet wins: 1 trophy for
|
||||
rounds 1–5, 2 for round 6. Draws award nothing.
|
||||
enemy pet as it's played. A clash that changes nothing is a stalemate:
|
||||
both pets faint so the ones behind them can settle it. Last player able
|
||||
to field a pet wins: 1 trophy for rounds 1–5, 2 for round 6. A battle
|
||||
that runs both sides out is a draw, and draws award nothing.
|
||||
|
||||
Apples and bees are **temporary**: they leave your deck after the battle.
|
||||
Most trophies after round 6 wins.
|
||||
|
||||
+18
-12
@@ -392,8 +392,8 @@ func effectCount(e Effect, s *battleSide, u *BattleUnit, enemy *battleSide) int
|
||||
// pet with Damage >= Power faints, firing Faint effects. Survivors that
|
||||
// took damage fire Hurt effects. Shields (Turtle, Gorilla, Melon) block
|
||||
// entire hits; Garlic shaves 1 from each; Scorpion KOs whatever its clash
|
||||
// attack manages to hurt. A clash that changes nothing ends the battle as a
|
||||
// stalemate.
|
||||
// attack manages to hurt. A clash that changes nothing is a stalemate: that
|
||||
// pair both faint so the pets behind them can settle the battle.
|
||||
//
|
||||
// resolveBattles is the orchestrator: it runs each (deterministic) simulation
|
||||
// and publishes the completed results.
|
||||
@@ -1807,12 +1807,18 @@ func (g *Game) runBattle(first, second int) *BattleResult {
|
||||
if dealtB > 0 && ua.hasKnockout() {
|
||||
ub.Damage = max(ub.Damage, ub.Power())
|
||||
}
|
||||
// Stalemate: the clash changed nothing at all, so repeating it never
|
||||
// will either — this pair is stuck no matter how many exchanges it
|
||||
// gets. Resolve it like a deadlock (below) rather than ending the
|
||||
// battle: the pets *behind* these two can still settle it.
|
||||
stuck := ua.Alive() && ub.Alive() && dealtA == 0 && dealtB == 0 &&
|
||||
blockA == nil && blockB == nil && prevA == nil && prevB == nil
|
||||
// Deadlock guard: if the pair keeps surviving, count the exchanges and
|
||||
// force a mutual knockout once they hit the limit.
|
||||
deadlocked := false
|
||||
if ua.Alive() && ub.Alive() {
|
||||
clashStreak++
|
||||
if clashStreak >= deadlockLimit {
|
||||
if stuck || clashStreak >= deadlockLimit {
|
||||
deadlocked = true
|
||||
ua.Damage = max(ua.Damage, ua.Power())
|
||||
ub.Damage = max(ub.Damage, ub.Power())
|
||||
@@ -1823,6 +1829,9 @@ func (g *Game) runBattle(first, second int) *BattleResult {
|
||||
clashTxt := fmt.Sprintf("%s's %s and %s's %s trade blows.",
|
||||
pname(0), ua.Card.Name, pname(1), ub.Card.Name)
|
||||
switch da, db := !ua.Alive(), !ub.Alive(); {
|
||||
case stuck:
|
||||
clashTxt = fmt.Sprintf("%s's %s and %s's %s can't hurt each other — both faint.",
|
||||
pname(0), ua.Card.Name, pname(1), ub.Card.Name)
|
||||
case deadlocked:
|
||||
clashTxt = fmt.Sprintf("%s's %s and %s's %s are deadlocked after %d clashes — both faint.",
|
||||
pname(0), ua.Card.Name, pname(1), ub.Card.Name, deadlockLimit)
|
||||
@@ -1851,10 +1860,6 @@ func (g *Game) runBattle(first, second int) *BattleResult {
|
||||
if prevB != nil {
|
||||
emitPrevent(1, ub.Card.Name, prevB)
|
||||
}
|
||||
if ua.Alive() && ub.Alive() && dealtA == 0 && dealtB == 0 &&
|
||||
blockA == nil && blockB == nil && prevA == nil && prevB == nil {
|
||||
break // stalemate: nothing can ever change
|
||||
}
|
||||
dealt := []int{dealtA, dealtB}
|
||||
for seat, u := range []*BattleUnit{ua, ub} {
|
||||
if !u.Alive() {
|
||||
@@ -1874,15 +1879,16 @@ func (g *Game) runBattle(first, second int) *BattleResult {
|
||||
}
|
||||
|
||||
// A single side that can still field a pet wins; anything else (both out,
|
||||
// or a stalemate with pets on both sides) is a draw. We test canField,
|
||||
// not unit, because the loop can break the instant one side runs out while
|
||||
// the other's current pet has just fainted — that side still has pets left
|
||||
// in its stack (it simply wasn't refilled) and is the rightful winner.
|
||||
// or the iteration cap tripping with pets on both sides) is a draw. We test
|
||||
// canField, not unit, because the loop can break the instant one side runs
|
||||
// out while the other's current pet has just fainted — that side still has
|
||||
// pets left in its stack (it simply wasn't refilled) and is the rightful
|
||||
// winner.
|
||||
winner := -1
|
||||
for side, s := range sides {
|
||||
if s.canField() {
|
||||
if winner >= 0 {
|
||||
winner = -1 // both still standing: a stalemate draw
|
||||
winner = -1 // both still standing: a draw
|
||||
break
|
||||
}
|
||||
winner = side
|
||||
|
||||
@@ -24,3 +24,25 @@ func TestCrocodileLastPetVolleyFiresWhenOwnerOut(t *testing.T) {
|
||||
t.Fatalf("both sides should be out (draw), got winner %d", res.WinnerSeat)
|
||||
}
|
||||
}
|
||||
|
||||
// Regression: a pair that can't hurt each other (two 0-attack pets, here
|
||||
// Spooked down to nothing) must both faint so the pets behind them settle the
|
||||
// battle. Previously the stalemate ended the whole battle in a draw, even with
|
||||
// most of both decks still to come.
|
||||
func TestStalematedPairFaintsAndBattleContinues(t *testing.T) {
|
||||
g, p1, _ := unicornGame(t)
|
||||
p1.Mana = 2
|
||||
res := forceBattle(t, g,
|
||||
// Nightcrawler (1 power) Spooks Barghest twice; Barghest (1 power)
|
||||
// Spooks it once. Neither can deal damage, but Pengobble is next up.
|
||||
[]Card{g.unicornPet(t, "Nightcrawler"), g.unicornPet(t, "Pengobble")},
|
||||
[]Card{g.unicornPet(t, "Barghest"), g.pet("Chaff", 1)},
|
||||
)
|
||||
clashes := eventsOfType(res, "clash")
|
||||
if len(clashes) != 1 || !clashes[0].Died[0] || !clashes[0].Died[1] {
|
||||
t.Fatalf("the stuck pair should both faint in one clash: %+v", clashes)
|
||||
}
|
||||
if res.WinnerSeat != 0 {
|
||||
t.Fatalf("Pengobble should go on to win for seat 0, got winner %d", res.WinnerSeat)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user