346 lines
8.2 KiB
Go
Executable File
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")
|
|
}
|
|
}
|