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

346 lines
8.2 KiB
Go
Executable File

package items
import (
"testing"
"wuziqi-server/core"
"github.com/heroiclabs/nakama-common/runtime"
)
// --- Mocks ---
type MockLogger struct {
runtime.Logger // Embed interface to skip implementing all methods
}
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
}
func (m *MockDispatcher) BroadcastMessage(opCode int64, data []byte, presences []runtime.Presence, sender runtime.Presence, reliable bool) error {
return nil
}
type MockGameLogic struct {
LastEvent *core.GameEvent
HealCalled bool
DamageCalls []struct {
TargetID string
Amount int
}
}
func (m *MockGameLogic) ApplyDamage(state *core.GameState, target *core.Player, amount int, isItemEffect bool) {
m.DamageCalls = append(m.DamageCalls, struct {
TargetID string
Amount int
}{TargetID: target.UserID, Amount: amount})
// Simulate basic damage for test state
target.HP -= amount
}
func (m *MockGameLogic) HealPlayer(player *core.Player, amount int) {
m.HealCalled = true
player.HP += amount
if player.HP > player.MaxHP {
player.HP = player.MaxHP
}
}
func (m *MockGameLogic) GetRandomAliveTarget(state *core.GameState, excludeID string) *core.Player {
for _, p := range state.Players {
if p.UserID != excludeID && p.HP > 0 {
return p
}
}
return nil
}
func (m *MockGameLogic) BroadcastEvent(event core.GameEvent) {
m.LastEvent = &event
}
func (m *MockGameLogic) SendPrivateEvent(targetID string, event core.GameEvent) {
m.LastEvent = &event
}
// --- Helper ---
func createTestContext(logic GameLogic) ItemContext {
return ItemContext{
Logger: &MockLogger{},
Dispatcher: &MockDispatcher{},
Logic: logic,
}
}
func createTestState() (*core.GameState, *core.Player) {
p1 := &core.Player{UserID: "p1", Username: "Player1", HP: 3, MaxHP: 4, Character: "dog", RevealedCells: make(map[int]string)}
p2 := &core.Player{UserID: "p2", Username: "Player2", HP: 4, MaxHP: 4, Character: "cat", RevealedCells: make(map[int]string)}
grid := make([]*core.GridCell, 100)
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,
}
return state, p1
}
// --- Tests ---
func TestMedkit(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &MedkitStrategy{}
// Setup: Poison user
user.Poisoned = true
user.PoisonSteps = 2
user.HP = 2
// Test Normal Use
consumed := strategy.Use(state, user, ctx)
if !consumed {
t.Error("Medkit should be consumed")
}
if user.Poisoned {
t.Error("Medkit should cure poison")
}
if user.PoisonSteps != 0 {
t.Error("Medkit should reset poison steps")
}
if user.HP != 3 {
t.Errorf("Medkit should heal 1 HP, got %d", user.HP)
}
if mockLogic.LastEvent == nil || mockLogic.LastEvent.ItemID != "medkit" {
t.Error("Medkit should broadcast event")
}
// Test Elephant Refusal
user.Character = "elephant"
consumed = strategy.Use(state, user, ctx)
if consumed {
t.Error("Medkit should NOT be consumed by Elephant")
}
}
func TestBombTimer(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &BombTimerStrategy{}
consumed := strategy.Use(state, user, ctx)
if !consumed {
t.Error("BombTimer should be consumed")
}
if user.TimeBombTurns != 3 {
t.Errorf("BombTimer should set turns to 3, got %d", user.TimeBombTurns)
}
}
func TestPoison(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &PoisonStrategy{}
// Target is p2 (available)
consumed := strategy.Use(state, user, ctx)
if !consumed {
t.Error("Poison should be consumed")
}
target := state.Players["p2"]
if !target.Poisoned {
t.Error("Target should be poisoned")
}
// Test Sloth Resistance
target.Character = "sloth"
target.Poisoned = false
strategy.Use(state, user, ctx)
if target.Poisoned {
t.Error("Sloth should resist poison")
}
}
func TestShield(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &ShieldStrategy{}
if user.Shield {
t.Error("User should not have shield initially")
}
consumed := strategy.Use(state, user, ctx)
if !consumed {
t.Error("Shield should be consumed")
}
if !user.Shield {
t.Error("User should have shield")
}
}
func TestSkip(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &SkipStrategy{}
consumed := strategy.Use(state, user, ctx)
if !consumed {
t.Error("Skip should be consumed")
}
if !user.SkipTurn {
t.Error("User should skip turn")
}
if !user.Shield {
t.Error("Skip card should grant shield")
}
// Test Elephant
user.Character = "elephant"
user.SkipTurn = false
consumed = strategy.Use(state, user, ctx)
if !consumed {
t.Error("Elephant should now be able to use skip")
}
if !user.SkipTurn {
t.Error("Elephant skip turn should be true")
}
}
func TestMagnifier(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &MagnifierStrategy{}
// Make sure grid has items to reveal
state.Grid[5].Type = "bomb"
consumed := strategy.Use(state, user, ctx)
if !consumed {
t.Error("Magnifier should be consumed")
}
if len(user.RevealedCells) != 1 {
t.Errorf("Should reveal 1 cell, got %d", len(user.RevealedCells))
}
}
func TestKnife(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &KnifeStrategy{}
// Case 1: Normal Knife (1 dmg to random target)
consumed := strategy.Use(state, user, ctx)
if !consumed {
t.Error("Knife should be consumed")
}
if len(mockLogic.DamageCalls) != 1 {
t.Errorf("Knife should cause 1 damage call, got %d", len(mockLogic.DamageCalls))
}
if mockLogic.DamageCalls[0].TargetID != "p2" || mockLogic.DamageCalls[0].Amount != 1 {
t.Error("Knife should deal 1 dmg to p2")
}
// Case 2: Tiger Knife (2 dmg AOE)
mockLogic.DamageCalls = nil // reset
user.Character = "tiger"
// Add a third player to verify AOE
state.Players["p3"] = &core.Player{UserID: "p3", HP: 4, MaxHP: 4}
strategy.Use(state, user, ctx)
if len(mockLogic.DamageCalls) != 2 {
t.Errorf("Tiger Knife should hit all enemies (2), got %d", len(mockLogic.DamageCalls))
}
if mockLogic.DamageCalls[0].Amount != 2 {
t.Error("Tiger Knife should deal 2 dmg")
}
}
func TestRevive(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &ReviveStrategy{}
consumed := strategy.Use(state, user, ctx)
if !consumed {
t.Error("Revive should be consumed")
}
if !user.Revive {
t.Error("User should have Revive flag")
}
// Elephant
user.Character = "elephant"
user.Revive = false
consumed = strategy.Use(state, user, ctx)
if consumed {
t.Error("Elephant should check privilege")
}
}
func TestLightning(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &LightningStrategy{}
strategy.Use(state, user, ctx)
// Should hit ALL players (including self per logic? "Lightning hits ALL players")
// Let's check effects.go: "for _, p := range state.Players { ApplyDamage }"
// Yes, hits everyone.
if len(mockLogic.DamageCalls) != 2 {
t.Errorf("Lightning should hit 2 players, got %d", len(mockLogic.DamageCalls))
}
}
func TestChest(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &ChestStrategy{}
strategy.Use(state, user, ctx)
if user.ChestCount != 1 {
t.Error("Chest count should increment")
}
}
func TestCurse(t *testing.T) {
state, user := createTestState()
mockLogic := &MockGameLogic{}
ctx := createTestContext(mockLogic)
strategy := &CurseStrategy{}
strategy.Use(state, user, ctx)
if !user.Curse {
t.Error("User should be cursed")
}
}