224 lines
5.9 KiB
Go
Executable File
224 lines
5.9 KiB
Go
Executable File
package logic
|
|
|
|
import (
|
|
"testing"
|
|
"wuziqi-server/characters"
|
|
"wuziqi-server/core"
|
|
"wuziqi-server/items"
|
|
|
|
"github.com/heroiclabs/nakama-common/runtime"
|
|
)
|
|
|
|
// --- Mocks ---
|
|
|
|
type MockLogger struct {
|
|
runtime.Logger
|
|
}
|
|
|
|
func (m *MockLogger) Info(format string, v ...interface{}) {}
|
|
func (m *MockLogger) Warn(format string, v ...interface{}) {}
|
|
func (m *MockLogger) Error(format string, v ...interface{}) {}
|
|
func (m *MockLogger) Debug(format string, v ...interface{}) {}
|
|
|
|
type MockDispatcher struct {
|
|
runtime.MatchDispatcher
|
|
LastOpCode int64
|
|
LastData []byte
|
|
}
|
|
|
|
func (m *MockDispatcher) BroadcastMessage(opCode int64, data []byte, presences []runtime.Presence, sender runtime.Presence, reliable bool) error {
|
|
m.LastOpCode = opCode
|
|
m.LastData = data
|
|
return nil
|
|
}
|
|
|
|
func createTestEngine() (*GameEngine, *core.GameState) {
|
|
logger := &MockLogger{}
|
|
dispatcher := &MockDispatcher{}
|
|
charMgr := characters.NewCharacterManager(nil)
|
|
itemMgr := items.NewItemManager()
|
|
|
|
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr, nil, nil)
|
|
|
|
// Create simplified state
|
|
p1 := &core.Player{UserID: "p1", Username: "P1", HP: 4, MaxHP: 4, Character: "dog", RevealedCells: make(map[int]string)}
|
|
p2 := &core.Player{UserID: "p2", Username: "P2", HP: 4, MaxHP: 4, Character: "cat", RevealedCells: make(map[int]string)}
|
|
|
|
// Grid
|
|
grid := make([]*core.GridCell, 10)
|
|
for i := range grid {
|
|
grid[i] = &core.GridCell{Type: "empty", Revealed: false}
|
|
}
|
|
|
|
state := &core.GameState{
|
|
Players: map[string]*core.Player{"p1": p1, "p2": p2},
|
|
Grid: grid,
|
|
GridSize: 5, // irrelevant for simplified test
|
|
TurnOrder: []string{"p1", "p2"},
|
|
CurrentTurnIndex: 0,
|
|
GameStarted: true,
|
|
}
|
|
|
|
return engine, state
|
|
}
|
|
|
|
// --- Tests ---
|
|
|
|
func TestApplyDamage(t *testing.T) {
|
|
engine, state := createTestEngine()
|
|
target := state.Players["p1"]
|
|
|
|
// 1. Basic Damage
|
|
engine.ApplyDamage(state, target, 1, false)
|
|
if target.HP != 3 {
|
|
t.Errorf("Expected 3 HP, got %d", target.HP)
|
|
}
|
|
|
|
// 2. Shield Block
|
|
target.Shield = true
|
|
engine.ApplyDamage(state, target, 10, false)
|
|
if target.HP != 3 {
|
|
t.Error("Shield should block damage")
|
|
}
|
|
if target.Shield {
|
|
t.Error("Shield should be consumed")
|
|
}
|
|
|
|
// 3. Curse Damage (Non-Cat)
|
|
target.Curse = true
|
|
engine.ApplyDamage(state, target, 1, false)
|
|
// (3 - 1*2) = 1
|
|
if target.HP != 1 {
|
|
t.Errorf("Curse should double dmg, expected 1 HP, got %d", target.HP)
|
|
}
|
|
if target.Curse {
|
|
t.Error("Curse should be consumed")
|
|
}
|
|
|
|
// 4. Cat Damage Cap
|
|
cat := state.Players["p2"] // cat
|
|
engine.ApplyDamage(state, cat, 100, false)
|
|
if cat.HP != 3 {
|
|
t.Errorf("Cat should take 1 dmg (4->3), got %d HP", cat.HP)
|
|
}
|
|
|
|
// 5. Revive
|
|
target.Revive = true
|
|
engine.ApplyDamage(state, target, 1000, false) // Kill
|
|
if target.HP != 1 {
|
|
t.Errorf("Revive should set HP to 1, got %d", target.HP)
|
|
}
|
|
if target.Revive {
|
|
t.Error("Revive should be consumed")
|
|
}
|
|
}
|
|
|
|
func TestAdvanceTurn(t *testing.T) {
|
|
engine, state := createTestEngine()
|
|
// Order: p1, p2
|
|
// Current: 0 (p1)
|
|
|
|
// 1. Simple Advance
|
|
engine.AdvanceTurn(state)
|
|
if state.CurrentTurnIndex != 1 { // Should be p2
|
|
t.Errorf("Expected turn index 1 (p2), got %d", state.CurrentTurnIndex)
|
|
}
|
|
|
|
// 2. Wrap Around
|
|
engine.AdvanceTurn(state)
|
|
if state.CurrentTurnIndex != 0 { // Should be p1
|
|
t.Errorf("Expected turn index 0 (p1), got %d", state.CurrentTurnIndex)
|
|
}
|
|
|
|
// 3. Skip Turn
|
|
state.Players["p2"].SkipTurn = true
|
|
// Current p1. Advance -> p2 (skipped) -> p1
|
|
engine.AdvanceTurn(state)
|
|
if state.CurrentTurnIndex != 0 {
|
|
t.Errorf("Should skip p2 and return to p1, got index %d", state.CurrentTurnIndex)
|
|
}
|
|
if state.Players["p2"].SkipTurn {
|
|
t.Error("SkipTurn flag should be cleared")
|
|
}
|
|
|
|
// 4. Time Bomb Logic
|
|
p2 := state.Players["p2"]
|
|
p2.TimeBombTurns = 1
|
|
// Verify p2 takes damage on next turn
|
|
// Manually set turn to p1 so next is p2
|
|
state.CurrentTurnIndex = 0
|
|
engine.AdvanceTurn(state) // Move to p2. Bomb explodes.
|
|
// Bomb is 2 damage. p2 started with 3 HP (cat took 1 earlier test? No new state here)
|
|
// New state created fresh in this test func. p2 is fresh 4 HP Cat.
|
|
// Wait, Cat takes 1 dmg from bomb too?
|
|
// Logic says: `dmg := 2`. Then ApplyDamage.
|
|
// Cat ApplyDamage -> 1.
|
|
// So p2 should have 3 HP.
|
|
// Wait, BombTimer logic in AdvanceTurn sets dmg=2.
|
|
// ApplyDamage calls OnDamageTaken.
|
|
// CharacterManager.OnDamageTaken for "cat" returns 1.
|
|
// So Cat takes 1.
|
|
|
|
if p2.HP != 3 {
|
|
t.Errorf("Cat p2 should take 1 dmg from bomb, got HP %d", p2.HP)
|
|
}
|
|
if state.CurrentTurnIndex != 1 {
|
|
t.Error("Should still be p2's turn after bomb (unless died)")
|
|
}
|
|
|
|
// 5. Poison Logic
|
|
p1 := state.Players["p1"]
|
|
p1.Poisoned = true
|
|
p1.PoisonSteps = 0
|
|
state.CurrentTurnIndex = 1 // Set to p2 so next is p1
|
|
engine.AdvanceTurn(state) // Move to p1
|
|
|
|
// Poison steps increments to 1. No dmg (mod 2 == 0?)
|
|
// Code: `PoisonSteps++` (becomes 1). `if PoisonSteps%2 == 0`.
|
|
// 1 % 2 != 0. No damage.
|
|
|
|
if p1.HP != 4 { // Fresh p1 has 4
|
|
t.Error("Poison should not trigger on step 1")
|
|
}
|
|
|
|
// Loop back to p1 again
|
|
state.CurrentTurnIndex = 1
|
|
engine.AdvanceTurn(state) // Move to p1. Steps becomes 2. 2%2==0. Dmg 1.
|
|
if p1.HP != 3 {
|
|
t.Errorf("Poison should trigger on step 2, got HP %d", p1.HP)
|
|
}
|
|
}
|
|
|
|
func TestHandleMove(t *testing.T) {
|
|
engine, state := createTestEngine()
|
|
|
|
// Setup grid
|
|
state.Grid[0].Type = "bomb"
|
|
state.Grid[1].Type = "empty"
|
|
state.Grid[2].Type = "item"
|
|
state.Grid[2].ItemID = "medkit"
|
|
|
|
p1 := state.Players["p1"]
|
|
state.CurrentTurnIndex = 0 // p1 turn
|
|
|
|
// 1. Hit Bomb
|
|
engine.HandleMove(state, "p1", 0)
|
|
// Bomb dmg 2. p1 HP 4->2.
|
|
if p1.HP != 2 {
|
|
t.Errorf("Hit bomb, expected 2 HP, got %d", p1.HP)
|
|
}
|
|
// Turn should advance
|
|
if state.CurrentTurnIndex != 1 {
|
|
t.Error("Turn should advance after bomb")
|
|
}
|
|
|
|
// 2. Hit Item
|
|
state.CurrentTurnIndex = 0 // Force p1 turn again
|
|
engine.HandleMove(state, "p1", 2)
|
|
// Medkit self heal logic in engine->ItemManager->Strategy
|
|
// p1 HP 2->3.
|
|
if p1.HP != 3 {
|
|
t.Errorf("Item medkit should heal, expected 3 HP, got %d", p1.HP)
|
|
}
|
|
}
|