game/server/logic/logic_test.go

224 lines
5.9 KiB
Go

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)
}
}