game/server/logic/comprehensive_test.go
2026-04-20 16:07:22 +08:00

775 lines
22 KiB
Go
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package logic
import (
"testing"
"wuziqi-server/characters"
"wuziqi-server/core"
)
// ============================================================
// 道具逻辑测试 (11个道具)
// ============================================================
// 1. 医疗包恢复1点血量可以解除中毒效果
func TestItem_Medkit_Basic(t *testing.T) {
RunScenario(t, GameScenario{
Name: "医疗包-基础恢复",
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 3}, // MaxHP 会自动设置为4
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "medkit"},
},
Actions: []GameAction{
{Type: "damage", PlayerID: "p1", Value: 1}, // 先扣1血到2
{Type: "move", PlayerID: "p1", Value: 0}, // 使用医疗包恢复到3
},
Checks: []ScenarioCheck{
{PlayerID: "p1", Field: "hp", Expected: 3, Message: "医疗包应该恢复1点血量"},
},
})
}
func TestItem_Medkit_CurePoison(t *testing.T) {
RunScenario(t, GameScenario{
Name: "医疗包-解除中毒",
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4, Poisoned: true},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "medkit"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "p1", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "p1", Field: "poisoned", Expected: false, Message: "医疗包应该解除中毒"},
},
})
}
func TestItem_Medkit_ElephantCannotUse(t *testing.T) {
RunScenario(t, GameScenario{
Name: "医疗包-大象无法使用",
Players: []PlayerSetup{
{ID: "elephant", Character: "elephant", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "medkit"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "elephant", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "elephant", Field: "hp", Expected: 4, Message: "大象无法使用医疗包"},
},
})
}
// 2. 定时炸弹踩到后3回合后爆炸扣除2点血量
func TestItem_BombTimer_Activation(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
{ID: "p2", Character: "cat", HP: 3},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "bomb_timer"},
},
})
// p1 踩到定时炸弹
engine.HandleMove(state, "p1", 0)
if state.Players["p1"].TimeBombTurns != 3 {
t.Errorf("定时炸弹应该设置3回合倒计时got %d", state.Players["p1"].TimeBombTurns)
}
}
func TestItem_BombTimer_Explosion(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4, TimeBomb: 1}, // 1回合后爆炸
{ID: "p2", Character: "cat", HP: 3},
},
Grid: []CellSetup{},
})
// 推进回合,炸弹应该爆炸
state.CurrentTurnIndex = 1 // 设置为p2下一个是p1
engine.AdvanceTurn(state)
if state.Players["p1"].HP != 2 {
t.Errorf("定时炸弹爆炸应该造成2点伤害HP应该是2got %d", state.Players["p1"].HP)
}
}
func TestItem_BombTimer_SlothReduction(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "sloth", Character: "sloth", HP: 4, TimeBomb: 1},
{ID: "p2", Character: "cat", HP: 3},
},
Grid: []CellSetup{},
})
state.CurrentTurnIndex = 1
engine.AdvanceTurn(state)
if state.Players["sloth"].HP != 3 {
t.Errorf("树懒定时炸弹伤害应该减为1HP应该是3got %d", state.Players["sloth"].HP)
}
}
// 3. 毒药瓶随机让一个存活玩家中毒每2回合扣1血
func TestItem_Poison_BasicEffect(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
{ID: "p2", Character: "monkey", HP: 4}, // 非树懒
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "poison"},
},
})
engine.HandleMove(state, "p1", 0)
// p2 应该中毒(只有一个非使用者目标)
if !state.Players["p2"].Poisoned {
t.Error("毒药应该使目标中毒")
}
}
func TestItem_Poison_DamagePerTwoTurns(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4, Poisoned: true},
{ID: "p2", Character: "cat", HP: 3},
},
Grid: []CellSetup{},
})
// 第1回合PoisonSteps=1不扣血
state.CurrentTurnIndex = 1
engine.AdvanceTurn(state)
if state.Players["p1"].HP != 4 {
t.Errorf("第1回合不应该扣血got HP=%d", state.Players["p1"].HP)
}
// 第2回合PoisonSteps=2扣1血
state.CurrentTurnIndex = 1
engine.AdvanceTurn(state)
if state.Players["p1"].HP != 3 {
t.Errorf("第2回合应该扣1血got HP=%d", state.Players["p1"].HP)
}
}
func TestItem_Poison_SlothImmune(t *testing.T) {
RunScenario(t, GameScenario{
Name: "毒药-树懒免疫",
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
{ID: "sloth", Character: "sloth", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "poison"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "p1", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "sloth", Field: "poisoned", Expected: false, Message: "树懒应该免疫毒药"},
},
})
}
// 4. 护盾免疫1次伤害
func TestItem_Shield_BlockDamage(t *testing.T) {
RunScenario(t, GameScenario{
Name: "护盾-抵挡伤害",
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4, Shield: true},
},
Grid: []CellSetup{
{Index: 0, Type: "bomb"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "p1", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "p1", Field: "hp", Expected: 4, Message: "护盾应该抵挡炸弹伤害"},
{PlayerID: "p1", Field: "shield", Expected: false, Message: "护盾应该被消耗"},
},
})
}
// 5. 好人卡:跳过回合+护盾
func TestItem_Skip_Effect(t *testing.T) {
RunScenario(t, GameScenario{
Name: "好人卡-跳过回合并获得护盾",
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
{ID: "p2", Character: "cat", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "skip"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "p1", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "p1", Field: "skip_turn", Expected: true, Message: "应该设置跳过回合"},
{PlayerID: "p1", Field: "shield", Expected: true, Message: "应该获得护盾"},
{PlayerID: "p2", Field: "current_turn", Expected: true, Message: "回合应该切换到p2"},
},
})
}
func TestItem_Skip_ElephantCanUse(t *testing.T) {
RunScenario(t, GameScenario{
Name: "好人卡-大象现在可以使用",
Players: []PlayerSetup{
{ID: "elephant", Character: "elephant", HP: 5},
{ID: "p2", Character: "dog", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "skip"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "elephant", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "elephant", Field: "skip_turn", Expected: true, Message: "大象现在应该可以使用好人卡"},
{PlayerID: "elephant", Field: "shield", Expected: true, Message: "大象现在应该获得护盾"},
},
})
}
// 6. 放大镜:透视一个随机格子
func TestItem_Magnifier_RevealCell(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "magnifier"},
{Index: 5, Type: "bomb"}, // 未揭示的炸弹
},
})
engine.HandleMove(state, "p1", 0)
if len(state.Players["p1"].RevealedCells) == 0 {
t.Error("放大镜应该揭示一个格子")
}
}
// 7. 飞刀对随机敌人造成1点伤害
func TestItem_Knife_BasicDamage(t *testing.T) {
RunScenario(t, GameScenario{
Name: "飞刀-基础伤害",
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
{ID: "p2", Character: "monkey", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "knife"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "p1", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "p2", Field: "hp", Expected: 3, Message: "飞刀应该造成1点伤害"},
{PlayerID: "p1", Field: "hp", Expected: 4, Message: "使用者不受伤害"},
},
})
}
func TestItem_Knife_TigerAOE(t *testing.T) {
RunScenario(t, GameScenario{
Name: "飞刀-老虎在场变全体2点伤害",
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
{ID: "p2", Character: "monkey", HP: 4},
{ID: "tiger", Character: "tiger", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "knife"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "p1", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "p2", Field: "hp", Expected: 2, Message: "老虎在场飞刀应该造成2点伤害"},
{PlayerID: "tiger", Field: "hp", Expected: 2, Message: "老虎自己也受2点伤害"},
{PlayerID: "p1", Field: "hp", Expected: 4, Message: "使用者不受伤害"},
},
})
}
// 8. 复活甲免疫一次死亡保留1点血量
func TestItem_Revive_SaveFromDeath(t *testing.T) {
RunScenario(t, GameScenario{
Name: "复活甲-免疫死亡",
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 1, Revive: true},
},
Grid: []CellSetup{
{Index: 0, Type: "bomb"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "p1", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "p1", Field: "hp", Expected: 1, Message: "复活甲应该保留1点血量"},
{PlayerID: "p1", Field: "alive", Expected: true, Message: "玩家应该存活"},
{PlayerID: "p1", Field: "revive", Expected: false, Message: "复活甲应该被消耗"},
},
})
}
func TestItem_Revive_ElephantCannotUse(t *testing.T) {
RunScenario(t, GameScenario{
Name: "复活甲-大象无法使用",
Players: []PlayerSetup{
{ID: "elephant", Character: "elephant", HP: 5},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "revive"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "elephant", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "elephant", Field: "revive", Expected: false, Message: "大象无法使用复活甲"},
},
})
}
// 9. 闪电对所有玩家造成1点伤害
func TestItem_Lightning_AllDamage(t *testing.T) {
RunScenario(t, GameScenario{
Name: "闪电-全体伤害",
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
{ID: "p2", Character: "monkey", HP: 4},
{ID: "p3", Character: "tiger", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "lightning"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "p1", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "p1", Field: "hp", Expected: 3, Message: "闪电对使用者也造成伤害"},
{PlayerID: "p2", Field: "hp", Expected: 3, Message: "闪电对p2造成伤害"},
{PlayerID: "p3", Field: "hp", Expected: 3, Message: "闪电对p3造成伤害"},
},
})
}
// 10. 宝箱:游戏结束后获得奖励
func TestItem_Chest_Counter(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "chest"},
},
})
engine.HandleMove(state, "p1", 0)
if state.Players["p1"].ChestCount != 1 {
t.Errorf("宝箱计数应该是1got %d", state.Players["p1"].ChestCount)
}
}
// 11. 诅咒:下次受伤翻倍
func TestItem_Curse_DoubleDamage(t *testing.T) {
RunScenario(t, GameScenario{
Name: "诅咒-伤害翻倍",
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4, Curse: true},
},
Grid: []CellSetup{
{Index: 0, Type: "bomb"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "p1", Value: 0}, // 炸弹2伤害 * 2 = 4
},
Checks: []ScenarioCheck{
{PlayerID: "p1", Field: "hp", Expected: 0, Message: "诅咒应该让伤害翻倍"},
{PlayerID: "p1", Field: "curse", Expected: false, Message: "诅咒应该被消耗"},
},
})
}
func TestItem_Curse_CatIgnores(t *testing.T) {
RunScenario(t, GameScenario{
Name: "诅咒-猫咪无视翻倍",
Players: []PlayerSetup{
{ID: "cat", Character: "cat", HP: 3, Curse: true},
},
Grid: []CellSetup{
{Index: 0, Type: "bomb"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "cat", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "cat", Field: "hp", Expected: 2, Message: "猫咪诅咒也只受1点伤害"},
},
})
}
// ============================================================
// 角色逻辑测试 (8个角色)
// ============================================================
// 1. 大象5点血无法使用好人卡/医疗包/复活甲
func TestCharacter_Elephant_HP(t *testing.T) {
charMgr := characters.NewCharacterManager(nil)
hp := charMgr.GetInitialHP("elephant", 4)
if hp != 5 {
t.Errorf("大象初始血量应该是5got %d", hp)
}
}
func TestCharacter_Elephant_ItemRestrictions(t *testing.T) {
// 已在上面的道具测试中覆盖
t.Log("大象道具限制已在道具测试中覆盖")
}
// 2. 猫咪3点血所有伤害强制为1包括诅咒
func TestCharacter_Cat_HP(t *testing.T) {
charMgr := characters.NewCharacterManager(nil)
hp := charMgr.GetInitialHP("cat", 4)
if hp != 3 {
t.Errorf("猫咪初始血量应该是3got %d", hp)
}
}
func TestCharacter_Cat_DamageCap(t *testing.T) {
RunScenario(t, GameScenario{
Name: "猫咪-伤害限制为1",
Players: []PlayerSetup{
{ID: "cat", Character: "cat", HP: 3},
},
Grid: []CellSetup{
{Index: 0, Type: "bomb"}, // 2点伤害
},
Actions: []GameAction{
{Type: "move", PlayerID: "cat", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "cat", Field: "hp", Expected: 2, Message: "猫咪受到任何伤害都只扣1点"},
},
})
}
// 3. 汪汪4(6)人赛每6(9)回合触发放大镜
func TestCharacter_Dog_MagnifierAbility(t *testing.T) {
// 由于狗狗技能依赖于 GlobalTurnCount % 6 == 0
// 且只有狗狗自己行动时才会触发,我们直接测试逻辑
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "dog", Character: "dog", HP: 4},
},
Grid: []CellSetup{},
})
// 创建足够的格子
state.Grid = make([]*core.GridCell, 100)
for i := range state.Grid {
state.Grid[i] = &core.GridCell{Type: "empty", Revealed: false}
}
state.Grid[99].Type = "bomb" // 放一个未揭示的炸弹
// 狗狗独立步数设为 5下一次操作将使其变为 6
state.Players["dog"].DogStepCount = 5
// 狗狗操作DogStepCount 变为 66 % 6 == 0应该触发
engine.HandleMove(state, "dog", 0)
t.Logf("操作后 DogStepCount=%d, RevealedCells=%d",
state.Players["dog"].DogStepCount, len(state.Players["dog"].RevealedCells))
// 检查是否触发了放大镜
if len(state.Players["dog"].RevealedCells) == 0 {
t.Errorf("狗狗应该在 DogStepCount=6 时触发放大镜能力")
} else {
t.Logf("狗狗放大镜触发成功,揭示了 %d 个格子", len(state.Players["dog"].RevealedCells))
}
}
// 4. 吉吉国王(猴子)每回合15%概率获得香蕉恢复1血最多2次
func TestCharacter_Monkey_BananaAbility(t *testing.T) {
// 由于是概率性的,我们测试计数器限制
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "monkey", Character: "monkey", HP: 2},
},
Grid: []CellSetup{},
})
// 手动设置已触发2次
state.Players["monkey"].MonkeyBananaCount = 2
// 模拟移动,不应该再触发
for i := 0; i < 10; i++ {
state.Grid = append(state.Grid, &core.GridCell{Type: "empty", Revealed: false})
}
initialHP := state.Players["monkey"].HP
engine.HandleMove(state, "monkey", 0)
// 由于已经触发2次不应该再恢复
if state.Players["monkey"].MonkeyBananaCount > 2 {
t.Error("猴子香蕉能力最多触发2次")
}
t.Logf("猴子HP变化: %d -> %d (计数: %d)", initialHP, state.Players["monkey"].HP, state.Players["monkey"].MonkeyBananaCount)
}
// 5. 坤坤(小鸡)受伤8%概率获得道具最多2次
func TestCharacter_Chicken_ItemOnDamage(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "chicken", Character: "chicken", HP: 4},
},
Grid: []CellSetup{},
})
// 手动设置已触发2次
state.Players["chicken"].ChickenItemCount = 2
// 造成伤害,不应该再触发
engine.ApplyDamage(state, state.Players["chicken"], 1, false)
if state.Players["chicken"].ChickenItemCount > 2 {
t.Error("小鸡道具能力最多触发2次")
}
}
// 6. 懒懒(树懒):免疫毒药,炸弹伤害减半
func TestCharacter_Sloth_PoisonImmune(t *testing.T) {
// 已在毒药测试中覆盖
t.Log("树懒毒药免疫已在道具测试中覆盖")
}
func TestCharacter_Sloth_BombDamageReduction(t *testing.T) {
RunScenario(t, GameScenario{
Name: "树懒-炸弹伤害减半",
Players: []PlayerSetup{
{ID: "sloth", Character: "sloth", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "bomb"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "sloth", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "sloth", Field: "hp", Expected: 3, Message: "树懒踩炸弹只受1点伤害"},
},
})
}
// 7. 河马无法拾取道具55%概率免疫死亡最多1次
func TestCharacter_Hippo_CannotPickItems(t *testing.T) {
RunScenario(t, GameScenario{
Name: "河马-无法拾取道具",
Players: []PlayerSetup{
{ID: "hippo", Character: "hippo", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "shield"},
},
Actions: []GameAction{
{Type: "move", PlayerID: "hippo", Value: 0},
},
Checks: []ScenarioCheck{
{PlayerID: "hippo", Field: "shield", Expected: false, Message: "河马无法拾取道具"},
},
})
}
func TestCharacter_Hippo_DeathResistOnce(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "hippo", Character: "hippo", HP: 1},
},
Grid: []CellSetup{},
})
// 模拟多次死亡尝试
deathResistTriggered := 0
for i := 0; i < 100; i++ {
// 重置状态
state.Players["hippo"].HP = 1
state.Players["hippo"].HippoDeathImmune = false
engine.ApplyDamage(state, state.Players["hippo"], 10, false)
if state.Players["hippo"].HP > 0 {
deathResistTriggered++
if state.Players["hippo"].HippoDeathImmune != true {
t.Error("河马死亡抵抗标志应该被设置")
}
}
}
t.Logf("河马死亡抵抗触发次数: %d/100 (预期约55%%)", deathResistTriggered)
// 测试已触发后不再生效
state.Players["hippo"].HP = 1
state.Players["hippo"].HippoDeathImmune = true
engine.ApplyDamage(state, state.Players["hippo"], 10, false)
if state.Players["hippo"].HP > 0 {
t.Error("河马死亡抵抗应该只能触发一次")
}
}
// 8. 老虎飞刀变全体2点伤害
func TestCharacter_Tiger_KnifeEnhance(t *testing.T) {
// 已在飞刀测试中覆盖
t.Log("老虎飞刀增强已在道具测试中覆盖")
}
// ============================================================
// 游戏流程测试
// ============================================================
func TestGameFlow_GameOver(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
{ID: "p2", Character: "cat", HP: 1},
},
Grid: []CellSetup{
{Index: 0, Type: "bomb"},
},
})
// p2 踩炸弹死亡
state.CurrentTurnIndex = 1
engine.HandleMove(state, "p2", 0)
if state.WinnerID != "p1" {
t.Errorf("p1应该获胜got winner=%s", state.WinnerID)
}
if state.GameStarted {
t.Error("游戏应该结束")
}
}
func TestGameFlow_DrawLastManStanding(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 1},
{ID: "p2", Character: "cat", HP: 1},
},
Grid: []CellSetup{
{Index: 0, Type: "item", ItemID: "lightning"}, // 闪电对所有人造成1点伤害
},
})
// p1 使用闪电,所有人同时死亡
// 按照逻辑,闪电会依次调用 ApplyDamage 给 p1, p2
// p2 是最后一个被处理的(也是最后一个 HP 归零的),所以应该是 LastDeadPlayerID
engine.HandleMove(state, "p1", 0)
if state.WinnerID != "p2" {
t.Errorf("平局时由于p2是处理列表最后一个死亡的应该获胜。got winner=%s", state.WinnerID)
}
if state.GameStarted {
t.Error("游戏应该结束")
}
}
func TestGameFlow_SafeAreaExpansion(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
},
Grid: []CellSetup{},
})
// 创建3x3网格右下角有炸弹
state.GridSize = 3
state.Grid = make([]*core.GridCell, 9)
for i := range state.Grid {
state.Grid[i] = &core.GridCell{Type: "empty", Revealed: false, NeighborBombs: 0}
}
state.Grid[8].Type = "bomb"
state.Grid[4].NeighborBombs = 1 // 中间格子有1个邻居炸弹
state.Grid[5].NeighborBombs = 1
state.Grid[7].NeighborBombs = 1
// 点击左上角
engine.HandleMove(state, "p1", 0)
// 统计揭示的格子
revealedCount := 0
for _, cell := range state.Grid {
if cell.Revealed {
revealedCount++
}
}
t.Logf("揭示的格子数: %d", revealedCount)
if revealedCount < 5 {
t.Errorf("安全区扩散应该揭示更多格子,只揭示了 %d 个", revealedCount)
}
if state.Grid[8].Revealed {
t.Error("炸弹不应该被揭示")
}
}
func TestGameFlow_TurnAdvance(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
{ID: "p2", Character: "cat", HP: 3},
{ID: "p3", Character: "tiger", HP: 4},
},
Grid: []CellSetup{
{Index: 0, Type: "empty"},
},
})
// p1 移动
engine.HandleMove(state, "p1", 0)
// 回合应该推进到 p2
if state.TurnOrder[state.CurrentTurnIndex] != "p2" {
t.Errorf("回合应该推进到p2当前是 %s", state.TurnOrder[state.CurrentTurnIndex])
}
}
func TestGameFlow_SkipTurn(t *testing.T) {
engine, state := createScenarioState(GameScenario{
Players: []PlayerSetup{
{ID: "p1", Character: "dog", HP: 4},
{ID: "p2", Character: "cat", HP: 3, SkipTurn: true},
{ID: "p3", Character: "tiger", HP: 4},
},
Grid: []CellSetup{},
})
// 从p1推进应该跳过p2到p3
state.CurrentTurnIndex = 0
engine.AdvanceTurn(state)
if state.TurnOrder[state.CurrentTurnIndex] != "p3" {
t.Errorf("应该跳过p2到p3当前是 %s", state.TurnOrder[state.CurrentTurnIndex])
}
}