775 lines
22 KiB
Go
Executable File
775 lines
22 KiB
Go
Executable File
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应该是2,got %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("树懒定时炸弹伤害应该减为1,HP应该是3,got %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("宝箱计数应该是1,got %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("大象初始血量应该是5,got %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("猫咪初始血量应该是3,got %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 变为 6,6 % 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])
|
||
}
|
||
}
|