Go实现简单的游戏内战斗

武器、子弹、普通伤害、aoe伤害、减速等

package game

import (
	"fmt"
	"log"
	"math"
	"math/rand"
	"sync"
	"time"
)

// Position 坐标位置
type Position struct {
	X float64
	Y float64
}

// Distance 计算两点之间的距离
func (p1 Position) Distance(p2 Position) float64 {
	return math.Sqrt(math.Pow(p1.X-p2.X, 2) + math.Pow(p1.Y-p2.Y, 2))
}

// MonsterType 怪物类型
type MonsterType string

const (
	MonsterTypeSlime  MonsterType = "slime"
	MonsterTypeGoblin MonsterType = "goblin"
	MonsterTypeOrc    MonsterType = "orc"
	MonsterTypeDragon MonsterType = "dragon"
)

// Monster 怪物结构体
type Monster struct {
	ID             string
	Type           MonsterType
	HP             int
	MaxHP          int
	Position       Position
	Speed          float64   // 基础速度
	CurrentSpeed   float64   // 当前速度(受减速效果影响)
	SpeedReduce    float64   // 减速比例(0.0-1.0)
	SpeedReduceEnd time.Time // 减速结束时间
	Stunned        bool      // 眩晕状态
	StunnedEnd     time.Time // 眩晕结束时间
	Burning        bool      // 燃烧状态
	BurnEnd        time.Time // 燃烧结束时间
	BurnDamage     int       // 每秒燃烧伤害
}

// Player 玩家结构体
type Player struct {
	ID       string
	HP       int
	MaxHP    int
	Position Position
	Attack   int
	Weapon   *Weapon
}

// WeaponType 武器类型
type WeaponType string

const (
	WeaponTypePistol     WeaponType = "pistol"     // 手枪 - 直线单发射击
	WeaponTypeShotgun    WeaponType = "shotgun"    // xian弹枪 - 扇形射击
	WeaponTypeRifle      WeaponType = "rifle"      // 步枪 - 直线穿透
	WeaponTypeSniper     WeaponType = "sniper"     // 狙击枪 - 高伤害直线
	WeaponTypeMachineGun WeaponType = "machinegun" // 机枪 - 快速连射
)

// BulletType 子弹类型
type BulletType string

const (
	BulletTypeNormal   BulletType = "normal"   // 普通子弹 - 直线
	BulletTypeShotgun  BulletType = "shotgun"  // xian弹 - 扇形扩散
	BulletTypePiercing BulletType = "piercing" // 穿透子弹
	BulletTypeSniper   BulletType = "sniper"   // 狙击子弹
)

// EffectType 范围效果类型
type EffectType string

const (
	EffectTypeFire   EffectType = "fire"   // 火焰 - 范围持续伤害
	EffectTypePoison EffectType = "poison" // 毒 - 范围持续伤害
	EffectTypeIce    EffectType = "ice"    // 冰冻 - 减速效果
	EffectTypeStun   EffectType = "stun"   // 眩晕 - 定身效果
	EffectTypeBurn   EffectType = "burn"   // 燃烧 - 挂载型持续伤害(挂载在怪物身上)
)

// AreaEffect 范围效果结构体
type AreaEffect struct {
	ID           string        // 效果ID
	Type         EffectType    // 效果类型
	Position     Position      // 中心位置
	Radius       float64       // 范围半径
	Damage       int           // 每跳伤害
	TickInterval time.Duration // 伤害间隔
	Duration     time.Duration // 持续时间
	Elapsed      time.Duration // 已过时间
	LastTick     time.Duration // 上次伤害时间
	Active       bool          // 是否激活
	SpeedReduce  float64       // 减速比例(冰冻效果用,0.0-1.0)
}

// EffectStrategy 范围效果策略接口
type EffectStrategy interface {
	OnTick(effect *AreaEffect, monster *Monster, game *Game) bool // 返回是否造成伤害
	OnCreate(effect *AreaEffect)
	GetName() string
}

// FireStrategy 火焰效果策略
type FireStrategy struct{}

func (s *FireStrategy) OnTick(effect *AreaEffect, monster *Monster, game *Game) bool {
	monster.HP -= effect.Damage
	log.Printf("火焰效果对怪物造成 %d 点伤害,剩余血量: %d", effect.Damage, monster.HP)
	return monster.HP <= 0
}

func (s *FireStrategy) OnCreate(effect *AreaEffect) {
	log.Printf("火焰效果已创建,范围: %.0f,持续时间: %v", effect.Radius, effect.Duration)
}

func (s *FireStrategy) GetName() string { return "火焰" }

// PoisonStrategy 毒效果策略
type PoisonStrategy struct{}

func (s *PoisonStrategy) OnTick(effect *AreaEffect, monster *Monster, game *Game) bool {
	monster.HP -= effect.Damage
	log.Printf("毒效果对怪物造成 %d 点伤害,剩余血量: %d", effect.Damage, monster.HP)
	return monster.HP <= 0
}

func (s *PoisonStrategy) OnCreate(effect *AreaEffect) {
	log.Printf("毒效果已创建,范围: %.0f,持续时间: %v", effect.Radius, effect.Duration)
}

func (s *PoisonStrategy) GetName() string { return "毒" }

// IceStrategy 冰冻效果策略
type IceStrategy struct{}

func (s *IceStrategy) OnTick(effect *AreaEffect, monster *Monster, game *Game) bool {
	// 冰冻效果不造成伤害,只减速
	// 减速逻辑已在updateAreaEffects中处理
	return false // 不造成死亡
}

func (s *IceStrategy) OnCreate(effect *AreaEffect) {
	log.Printf("冰冻效果已创建,范围: %.0f,持续时间: %v,减速: %.0f%%",
		effect.Radius, effect.Duration, effect.SpeedReduce*100)
}

func (s *IceStrategy) GetName() string { return "冰冻" }

// StunStrategy 眩晕效果策略
type StunStrategy struct{}

func (s *StunStrategy) OnTick(effect *AreaEffect, monster *Monster, game *Game) bool {
	// 眩晕效果不造成伤害,只眩晕
	// 眩晕逻辑已在updateAreaEffects中处理
	return false // 不造成死亡
}

func (s *StunStrategy) OnCreate(effect *AreaEffect) {
	log.Printf("眩晕效果已创建,范围: %.0f,持续时间: %v", effect.Radius, effect.Duration)
}

func (s *StunStrategy) GetName() string { return "眩晕" }

// BurnStrategy 燃烧效果策略(挂载型)
type BurnStrategy struct{}

func (s *BurnStrategy) OnTick(effect *AreaEffect, monster *Monster, game *Game) bool {
	// 燃烧效果不通过范围效果Tick处理,而是挂载在怪物身上
	// 实际燃烧伤害在updateMonsters中处理
	return false
}

func (s *BurnStrategy) OnCreate(effect *AreaEffect) {
	log.Printf("燃烧效果已创建,范围: %.0f,持续时间: %v", effect.Radius, effect.Duration)
}

func (s *BurnStrategy) GetName() string { return "燃烧" }

// GetEffectStrategy 获取效果策略
func GetEffectStrategy(effectType EffectType) EffectStrategy {
	switch effectType {
	case EffectTypeFire:
		return &FireStrategy{}
	case EffectTypePoison:
		return &PoisonStrategy{}
	case EffectTypeIce:
		return &IceStrategy{}
	case EffectTypeStun:
		return &StunStrategy{}
	case EffectTypeBurn:
		return &BurnStrategy{}
	default:
		return &FireStrategy{}
	}
}

// Weapon 武器结构体
type Weapon struct {
	ID               string
	Name             string
	Type             WeaponType
	Damage           int           // 基础伤害
	Range            float64       // 射程
	BulletSpeed      float64       // 子弹速度
	Penetration      bool          // 是否穿透
	MaxPenetration   int           // 最大穿透数量
	BulletCount      int           // 每次发射子弹数量(xian弹枪用)
	SpreadAngle      float64       // 扩散角度(弧度,xian弹枪用)
	FireRate         time.Duration // 射速间隔
	Level            int           // 武器等级
	DamageMultiplier float64       // 伤害倍率(随等级提升)
	BulletType       BulletType    // 子弹类型
	Knockback        float64       // 击退力度
	// 暴击系统
	CriticalChance     float64 // 暴击率(0.0-1.0)
	CriticalMultiplier float64 // 暴击伤害倍率
	// 弹药系统
	MaxAmmo     int           // 最大弹药量
	CurrentAmmo int           // 当前弹药量
	ReloadTime  time.Duration // 换弹时间
}

// ShootingStrategy 射击策略接口
type ShootingStrategy interface {
	Shoot(g *Game, weapon *Weapon, targetX, targetY float64)
}

// NormalStrategy 普通直线射击策略
type NormalStrategy struct{}

func (s *NormalStrategy) Shoot(g *Game, weapon *Weapon, targetX, targetY float64) {
	g.shootInternal(targetX, targetY, weapon)
}

// ShotgunStrategy xian弹枪扇形射击策略
type ShotgunStrategy struct{}

func (s *ShotgunStrategy) Shoot(g *Game, weapon *Weapon, targetX, targetY float64) {
	playerPos := g.player.Position

	// 计算基础方向
	dx := targetX - playerPos.X
	dy := targetY - playerPos.Y
	distance := math.Sqrt(dx*dx + dy*dy)

	if distance < 0.001 {
		return
	}

	baseDirX := dx / distance
	baseDirY := dy / distance

	// 使用公共方法计算扇形角度
	angles := CalculateSpreadAngles(weapon.BulletCount, weapon.SpreadAngle)

	// 生成扇形分布的子弹
	for _, angleOffset := range angles {
		// 使用公共方法计算方向
		dirX, dirY := CalculateDirectionWithSpread(baseDirX, baseDirY, angleOffset)
		// 使用公共方法创建子弹
		g.CreateBullet(playerPos.X, playerPos.Y, dirX, dirY, weapon)
	}
}

// GetShootingStrategy 获取射击策略
func GetShootingStrategy(weaponType WeaponType) ShootingStrategy {
	switch weaponType {
	case WeaponTypeShotgun:
		return &ShotgunStrategy{}
	case WeaponTypePistol, WeaponTypeRifle, WeaponTypeSniper, WeaponTypeMachineGun:
	default:
		return &NormalStrategy{}
	}
	return &NormalStrategy{}
}

// CalculateDamage 计算武器最终伤害
func CalculateDamage(weapon *Weapon) int {
	return int(float64(weapon.Damage) * weapon.DamageMultiplier)
}

// CalculateDamageMultiplier 计算伤害倍率(根据等级)
func CalculateDamageMultiplier(level int) float64 {
	return 1.0 + float64(level-1)*0.2 // 每级提升20%
}

// CalculateSpreadAngles 计算扇形分布的角度列表
// bulletCount: 子弹数量
// spreadAngle: 总扩散角度(弧度)
func CalculateSpreadAngles(bulletCount int, spreadAngle float64) []float64 {
	if bulletCount <= 1 {
		return []float64{0}
	}

	angles := make([]float64, bulletCount)
	halfAngle := spreadAngle / 2

	for i := 0; i < bulletCount; i++ {
		if bulletCount == 1 {
			angles[i] = 0
		} else {
			angles[i] = halfAngle*2*(float64(i)/(float64(bulletCount)-1)) - halfAngle
		}
	}

	return angles
}

// CalculateDirectionWithSpread 计算带角度偏移的方向向量
func CalculateDirectionWithSpread(baseDirX, baseDirY, angleOffset float64) (float64, float64) {
	cos := math.Cos(angleOffset)
	sin := math.Sin(angleOffset)
	return baseDirX*cos - baseDirY*sin, baseDirX*sin + baseDirY*cos
}

// CreateBullet 创建子弹
func (g *Game) CreateBullet(x, y, dirX, dirY float64, weapon *Weapon) {
	damage := CalculateDamage(weapon)
	isCritical := false

	// 暴击判定
	if weapon.CriticalChance > 0 && rand.Float64() < weapon.CriticalChance {
		damage = int(float64(damage) * weapon.CriticalMultiplier)
		isCritical = true
	}

	bullet := &Bullet{
		ID:                    "bullet_" + string(rune('a'+g.bulletID)),
		Position:              Position{X: x, Y: y},
		Direction:             Position{X: dirX, Y: dirY},
		Speed:                 weapon.BulletSpeed,
		Damage:                damage,
		Penetration:           weapon.Penetration,
		MaxPenetration:        weapon.MaxPenetration,
		RemainingPenetrations: weapon.MaxPenetration,
		Active:                true,
		BulletType:            weapon.BulletType,
		Knockback:             weapon.Knockback,
		Critical:              isCritical, // 标记是否为暴击
	}

	g.bulletID++
	g.bullets[bullet.ID] = bullet

	if isCritical {
		log.Printf("创建暴击子弹ID: %s,方向: (%f, %f),暴击伤害: %d", bullet.ID, dirX, dirY, damage)
	} else {
		log.Printf("创建子弹ID: %s,方向: (%f, %f),伤害: %d", bullet.ID, dirX, dirY, damage)
	}
}

// Bullet 子弹结构体
type Bullet struct {
	ID                    string
	Position              Position
	Direction             Position // 移动方向(单位向量)
	Speed                 float64
	Damage                int
	Penetration           bool
	MaxPenetration        int
	RemainingPenetrations int // 剩余穿透次数
	Active                bool
	BulletType            BulletType
	Knockback             float64 // 击退力度
	Critical              bool    // 是否为暴击
}

// GameConfig 游戏配置
type GameConfig struct {
	MapWidth             float64
	MapHeight            float64
	MonsterSpawnInterval time.Duration
	UpdateInterval       time.Duration // 游戏状态更新间隔
	AttackRange          float64
	PlayerStartHP        int
	AutoShootRange       float64       // 自动射击范围
	AutoShootInterval    time.Duration // 自动射击间隔
}

// Game 游戏核心结构体
type Game struct {
	config          GameConfig
	player          *Player
	monsters        map[string]*Monster
	bullets         map[string]*Bullet
	areaEffects     map[string]*AreaEffect // 范围效果列表
	mu              sync.RWMutex
	running         bool
	spawnTicker     *time.Ticker // 怪物生成定时器
	updateTicker    *time.Ticker // 游戏状态更新定时器
	autoShootTicker *time.Ticker // 自动射击定时器
	gameOver        bool
	monsterID       int
	bulletID        int
	effectID        int
	lastFireTime    map[string]time.Time // 每种武器的上次射击时间(用于冷却控制)
	reloading       map[string]bool      // 每种武器是否正在换弹
}

// NewGame 创建新游戏
func NewGame(config GameConfig) *Game {
	// 设置默认值(-1表示关闭自动射击)
	if config.AutoShootRange < 0 {
		// 负值表示关闭自动射击,不设置默认值
	} else if config.AutoShootRange == 0 {
		config.AutoShootRange = 200 // 默认自动射击范围
	}
	if config.AutoShootInterval <= 0 {
		config.AutoShootInterval = 500 * time.Millisecond // 默认自动射击间隔
	}
	// AutoShootRange < 0 表示关闭自动射击

	return &Game{
		config:      config,
		monsters:    make(map[string]*Monster),
		bullets:     make(map[string]*Bullet),
		areaEffects: make(map[string]*AreaEffect),
		player: &Player{
			ID:       "player",
			HP:       config.PlayerStartHP,
			MaxHP:    config.PlayerStartHP,
			Position: Position{X: config.MapWidth / 2, Y: config.MapHeight / 2},
			Attack:   10,
			Weapon:   NewWeapon(WeaponTypePistol, 1), // 初始武器:1级手枪
		},
		lastFireTime: make(map[string]time.Time),
		reloading:    make(map[string]bool),
	}
}

// NewWeapon 创建武器
func NewWeapon(weaponType WeaponType, level int) *Weapon {
	weapon := &Weapon{
		Type:             weaponType,
		Level:            level,
		DamageMultiplier: CalculateDamageMultiplier(level), // 使用公共方法计算伤害倍率
	}

	switch weaponType {
	case WeaponTypePistol:
		weapon.ID = "weapon_pistol"
		weapon.Name = "手枪"
		weapon.Damage = 15
		weapon.Range = 300
		weapon.BulletSpeed = 15
		weapon.Penetration = false
		weapon.MaxPenetration = 1
		weapon.BulletCount = 1
		weapon.SpreadAngle = 0
		weapon.FireRate = 500 * time.Millisecond
		weapon.BulletType = BulletTypeNormal
		weapon.CriticalChance = 0.1     // 10%暴击率
		weapon.CriticalMultiplier = 2.0 // 暴击伤害2倍
		weapon.MaxAmmo = 12             // 12发弹药
		weapon.ReloadTime = 1500 * time.Millisecond

	case WeaponTypeShotgun:
		weapon.ID = "weapon_shotgun"
		weapon.Name = "xian弹枪"
		weapon.Damage = 10
		weapon.Range = 150
		weapon.BulletSpeed = 12
		weapon.Penetration = false
		weapon.MaxPenetration = 1
		weapon.BulletCount = 5
		weapon.SpreadAngle = math.Pi / 6 // 30度扇形
		weapon.FireRate = 1000 * time.Millisecond
		weapon.BulletType = BulletTypeShotgun
		weapon.Knockback = 30           // xian弹枪有较大击退力
		weapon.CriticalChance = 0.05    // 5%暴击率
		weapon.CriticalMultiplier = 1.5 // 暴击伤害1.5倍
		weapon.MaxAmmo = 6              // 6发弹药
		weapon.ReloadTime = 2000 * time.Millisecond

	case WeaponTypeRifle:
		weapon.ID = "weapon_rifle"
		weapon.Name = "步枪"
		weapon.Damage = 25
		weapon.Range = 400
		weapon.BulletSpeed = 25
		weapon.Penetration = true
		weapon.MaxPenetration = 3
		weapon.BulletCount = 1
		weapon.SpreadAngle = 0
		weapon.FireRate = 600 * time.Millisecond
		weapon.BulletType = BulletTypePiercing
		weapon.Knockback = 10           // 步枪有小击退力
		weapon.CriticalChance = 0.15    // 15%暴击率
		weapon.CriticalMultiplier = 2.0 // 暴击伤害2倍
		weapon.MaxAmmo = 30             // 30发弹药
		weapon.ReloadTime = 1500 * time.Millisecond

	case WeaponTypeSniper:
		weapon.ID = "weapon_sniper"
		weapon.Name = "狙击枪"
		weapon.Damage = 50
		weapon.Range = 600
		weapon.BulletSpeed = 40
		weapon.Penetration = true
		weapon.MaxPenetration = 2
		weapon.BulletCount = 1
		weapon.SpreadAngle = 0
		weapon.FireRate = 2000 * time.Millisecond
		weapon.BulletType = BulletTypeSniper
		weapon.Knockback = 50           // 狙击枪有极大击退力
		weapon.CriticalChance = 0.3     // 30%暴击率
		weapon.CriticalMultiplier = 2.5 // 暴击伤害2.5倍
		weapon.MaxAmmo = 5              // 5发弹药
		weapon.ReloadTime = 3000 * time.Millisecond

	case WeaponTypeMachineGun:
		weapon.ID = "weapon_machinegun"
		weapon.Name = "机枪"
		weapon.Damage = 8
		weapon.Range = 250
		weapon.BulletSpeed = 20
		weapon.Penetration = false
		weapon.MaxPenetration = 1
		weapon.BulletCount = 1
		weapon.SpreadAngle = 0
		weapon.FireRate = 100 * time.Millisecond
		weapon.BulletType = BulletTypeNormal
		weapon.CriticalChance = 0.05    // 5%暴击率
		weapon.CriticalMultiplier = 1.5 // 暴击伤害1.5倍
		weapon.MaxAmmo = 100            // 100发弹药
		weapon.ReloadTime = 2500 * time.Millisecond
	}

	// 初始化当前弹药为最大弹药
	weapon.CurrentAmmo = weapon.MaxAmmo

	return weapon
}

// GetPlayer 获取玩家
func (g *Game) GetPlayer() *Player {
	g.mu.RLock()
	defer g.mu.RUnlock()
	return g.player
}

// GetMonsters 获取所有怪物
func (g *Game) GetMonsters() []*Monster {
	g.mu.RLock()
	defer g.mu.RUnlock()
	result := make([]*Monster, 0, len(g.monsters))
	for _, m := range g.monsters {
		result = append(result, m)
	}
	return result
}

// MovePlayer 移动玩家
// dx: X方向移动量(正数向右,负数向左)
// dy: Y方向移动量(正数向下,负数向上)
func (g *Game) MovePlayer(dx, dy float64) {
	g.mu.Lock()
	defer g.mu.Unlock()

	// 计算新位置
	newX := g.player.Position.X + dx
	newY := g.player.Position.Y + dy

	// 确保玩家不会移出地图边界
	if newX < 0 {
		newX = 0
	}
	if newX > g.config.MapWidth {
		newX = g.config.MapWidth
	}
	if newY < 0 {
		newY = 0
	}
	if newY > g.config.MapHeight {
		newY = g.config.MapHeight
	}

	// 更新玩家位置
	g.player.Position.X = newX
	g.player.Position.Y = newY
}

// Shoot 玩家射击(公共方法)
// targetX, targetY: 目标坐标
func (g *Game) Shoot(targetX, targetY float64) {
	g.mu.Lock()
	defer g.mu.Unlock()

	if g.gameOver || g.player.Weapon == nil {
		return
	}

	weapon := g.player.Weapon
	now := time.Now()

	// 检查是否正在换弹
	if g.reloading[string(weapon.Type)] {
		log.Printf("%s 正在换弹中,无法射击", weapon.Name)
		return
	}

	// 检查弹药
	if weapon.CurrentAmmo <= 0 {
		log.Printf("%s 弹药耗尽,自动换弹中", weapon.Name)
		go g.reload(weapon)
		return
	}

	// 检查冷却
	if lastTime, exists := g.lastFireTime[string(weapon.Type)]; exists {
		if now.Sub(lastTime) < weapon.FireRate {
			return // 冷却中
		}
	}

	// 更新射击时间
	g.lastFireTime[string(weapon.Type)] = now

	// 消耗弹药
	weapon.CurrentAmmo--

	// 获取对应的射击策略并执行
	strategy := GetShootingStrategy(weapon.Type)
	strategy.Shoot(g, weapon, targetX, targetY)
}

// reload 换弹方法
func (g *Game) reload(weapon *Weapon) {
	g.mu.Lock()
	g.reloading[string(weapon.Type)] = true
	g.mu.Unlock()

	// 等待换弹时间
	time.Sleep(weapon.ReloadTime)

	g.mu.Lock()
	defer g.mu.Unlock()

	// 换弹完成
	weapon.CurrentAmmo = weapon.MaxAmmo
	g.reloading[string(weapon.Type)] = false
	log.Printf("%s 换弹完成,当前弹药: %d/%d", weapon.Name, weapon.CurrentAmmo, weapon.MaxAmmo)
}

// shootInternal 内部射击方法(已持有锁)- 用于普通直线射击
func (g *Game) shootInternal(targetX, targetY float64, weapon *Weapon) {
	playerPos := g.player.Position

	// 计算射击方向
	dx := targetX - playerPos.X
	dy := targetY - playerPos.Y
	distance := math.Sqrt(dx*dx + dy*dy)

	if distance < 0.001 {
		return // 避免除以零
	}

	// 标准化方向向量
	dirX := dx / distance
	dirY := dy / distance

	// 使用公共方法创建子弹
	g.CreateBullet(playerPos.X, playerPos.Y, dirX, dirY, weapon)
}

// ToggleWeaponPenetration 切换武器穿透功能
func (g *Game) ToggleWeaponPenetration() {
	g.mu.Lock()
	defer g.mu.Unlock()

	if g.player.Weapon != nil {
		g.player.Weapon.Penetration = !g.player.Weapon.Penetration
		log.Printf("武器穿透功能 %s", map[bool]string{true: "开启", false: "关闭"}[g.player.Weapon.Penetration])
	}
}

// Start 开始游戏
func (g *Game) Start() {
	g.mu.Lock()
	if g.running {
		g.mu.Unlock()
		return
	}
	g.running = true
	g.gameOver = false
	g.mu.Unlock()

	// 启动怪物生成定时器
	g.spawnTicker = time.NewTicker(g.config.MonsterSpawnInterval)
	go func() {
		for {
			g.mu.Lock()
			if !g.running {
				g.mu.Unlock()
				return
			}
			g.mu.Unlock()

			<-g.spawnTicker.C
			g.spawnMonster()
		}
	}()

	// 启动游戏状态更新定时器
	g.updateTicker = time.NewTicker(g.config.UpdateInterval)
	go func() {
		for {
			g.mu.Lock()
			if !g.running {
				g.mu.Unlock()
				return
			}
			g.mu.Unlock()

			<-g.updateTicker.C
			g.updateInternal()
		}
	}()

	// 启动自动射击定时器
	g.autoShootTicker = time.NewTicker(g.config.AutoShootInterval)
	go func() {
		for {
			g.mu.Lock()
			if !g.running {
				g.mu.Unlock()
				return
			}
			g.mu.Unlock()

			<-g.autoShootTicker.C
			g.autoShoot()
		}
	}()
}

// autoShoot 自动射击:向范围内最近的怪物射击
func (g *Game) autoShoot() {
	g.mu.Lock()
	defer g.mu.Unlock()

	if g.gameOver || g.player.Weapon == nil {
		return
	}

	// 如果AutoShootRange < 0,表示关闭自动射击
	if g.config.AutoShootRange < 0 {
		return
	}

	playerPos := g.player.Position
	shootRange := g.config.AutoShootRange

	// 寻找范围内最近的怪物
	var nearestMonster *Monster
	minDistance := shootRange + 1 // 初始值设为超过范围

	for _, monster := range g.monsters {
		distance := playerPos.Distance(monster.Position)
		if distance <= shootRange && distance < minDistance {
			minDistance = distance
			nearestMonster = monster
		}
	}

	// 如果找到目标,射击(使用策略模式)
	if nearestMonster != nil {
		strategy := GetShootingStrategy(g.player.Weapon.Type)
		strategy.Shoot(g, g.player.Weapon, nearestMonster.Position.X, nearestMonster.Position.Y)
	}
}

// CreateAreaEffect 创建范围效果
// effectType: 效果类型(火焰、毒等)
// x, y: 效果中心坐标
// radius: 范围半径
// damage: 每跳伤害
// tickInterval: 伤害间隔
// duration: 持续时间
func (g *Game) CreateAreaEffect(effectType EffectType, x, y float64, radius float64, damage int, tickInterval, duration time.Duration) {
	g.mu.Lock()
	defer g.mu.Unlock()

	effect := &AreaEffect{
		ID:           fmt.Sprintf("effect_%d", g.effectID),
		Type:         effectType,
		Position:     Position{X: x, Y: y},
		Radius:       radius,
		Damage:       damage,
		TickInterval: tickInterval,
		Duration:     duration,
		Active:       true,
	}
	g.effectID++

	// 调用效果策略的创建回调
	strategy := GetEffectStrategy(effectType)
	strategy.OnCreate(effect)

	g.areaEffects[effect.ID] = effect
}

// CreateFireEffect 便捷方法:创建火海
func (g *Game) CreateFireEffect(x, y, radius float64, damage int, duration time.Duration) {
	g.CreateAreaEffect(EffectTypeFire, x, y, radius, damage, 500*time.Millisecond, duration)
}

// CreatePoisonEffect 便捷方法:创建毒池
func (g *Game) CreatePoisonEffect(x, y, radius float64, damage int, duration time.Duration) {
	g.CreateAreaEffect(EffectTypePoison, x, y, radius, damage, 300*time.Millisecond, duration)
}

// CreateIceEffect 便捷方法:创建冰冻效果
// speedReduce: 减速比例(0.0-1.0,0.5表示减速50%)
func (g *Game) CreateIceEffect(x, y, radius float64, speedReduce float64, duration time.Duration) {
	g.mu.Lock()
	defer g.mu.Unlock()

	effect := &AreaEffect{
		ID:           fmt.Sprintf("effect_%d", g.effectID),
		Type:         EffectTypeIce,
		Position:     Position{X: x, Y: y},
		Radius:       radius,
		Damage:       0,                      // 冰冻不造成伤害
		TickInterval: 100 * time.Millisecond, // 减速检测间隔
		Duration:     duration,
		SpeedReduce:  speedReduce,
		Active:       true,
	}
	g.effectID++

	// 调用效果策略的创建回调
	strategy := GetEffectStrategy(effect.Type)
	strategy.OnCreate(effect)

	g.areaEffects[effect.ID] = effect
}

// CreateStunEffect 便捷方法:创建眩晕效果(范围眩晕)
func (g *Game) CreateStunEffect(x, y, radius float64, duration time.Duration) {
	g.mu.Lock()
	defer g.mu.Unlock()

	effect := &AreaEffect{
		ID:           fmt.Sprintf("effect_%d", g.effectID),
		Type:         EffectTypeStun,
		Position:     Position{X: x, Y: y},
		Radius:       radius,
		Damage:       0,                      // 眩晕不造成伤害
		TickInterval: 100 * time.Millisecond, // 检测间隔
		Duration:     duration,
		Active:       true,
	}
	g.effectID++

	// 调用效果策略的创建回调
	strategy := GetEffectStrategy(effect.Type)
	strategy.OnCreate(effect)

	g.areaEffects[effect.ID] = effect
}

// CreateBurnEffect 便捷方法:创建燃烧效果(挂载型持续伤害)
// 燃烧效果会挂载在范围内的怪物身上,不依赖范围效果Tick
func (g *Game) CreateBurnEffect(x, y, radius float64, burnDamage int, duration time.Duration) {
	g.mu.Lock()
	defer g.mu.Unlock()

	effect := &AreaEffect{
		ID:           fmt.Sprintf("effect_%d", g.effectID),
		Type:         EffectTypeBurn,
		Position:     Position{X: x, Y: y},
		Radius:       radius,
		Damage:       burnDamage,
		TickInterval: 500 * time.Millisecond, // 挂载检测间隔
		Duration:     duration,
		Active:       true,
	}
	g.effectID++

	// 遍历范围内的怪物,挂载燃烧效果
	for _, monster := range g.monsters {
		distance := effect.Position.Distance(monster.Position)
		if distance <= effect.Radius && !monster.Burning {
			monster.Burning = true
			monster.BurnEnd = time.Now().Add(effect.Duration)
			monster.BurnDamage = effect.Damage
			log.Printf("怪物 %s 被挂载燃烧效果,每秒伤害: %d,持续时间: %v", monster.ID, effect.Damage, effect.Duration)
		}
	}

	// 调用效果策略的创建回调
	strategy := GetEffectStrategy(effect.Type)
	strategy.OnCreate(effect)

	g.areaEffects[effect.ID] = effect
}

// Stop 停止游戏
func (g *Game) Stop() {
	g.mu.Lock()
	defer g.mu.Unlock()
	g.running = false
	if g.spawnTicker != nil {
		g.spawnTicker.Stop()
	}
	if g.updateTicker != nil {
		g.updateTicker.Stop()
	}
	if g.autoShootTicker != nil {
		g.autoShootTicker.Stop()
	}
}

// spawnMonster 生成怪物
func (g *Game) spawnMonster() {
	g.mu.Lock()
	defer g.mu.Unlock()

	if g.gameOver {
		return
	}

	// 随机选择怪物类型
	monsterTypes := []MonsterType{MonsterTypeSlime, MonsterTypeGoblin, MonsterTypeOrc, MonsterTypeDragon}
	monsterType := monsterTypes[rand.Intn(len(monsterTypes))]

	// 根据类型设置属性
	hp := getMonsterHP(monsterType)
	speed := getMonsterSpeed(monsterType)

	// 随机生成在地图边缘
	position := g.getRandomEdgePosition()

	monster := &Monster{
		ID:           "monster_" + string(rune('a'+g.monsterID)),
		Type:         monsterType,
		HP:           hp,
		MaxHP:        hp,
		Position:     position,
		Speed:        speed,
		CurrentSpeed: speed, // 初始化当前速度
	}
	g.monsterID++
	g.monsters[monster.ID] = monster
}

// getMonsterHP 获取怪物血量
func getMonsterHP(t MonsterType) int {
	switch t {
	case MonsterTypeSlime:
		return 20
	case MonsterTypeGoblin:
		return 40
	case MonsterTypeOrc:
		return 80
	case MonsterTypeDragon:
		return 200
	default:
		return 30
	}
}

// getMonsterSpeed 获取怪物速度
func getMonsterSpeed(t MonsterType) float64 {
	switch t {
	case MonsterTypeSlime:
		return 1.5
	case MonsterTypeGoblin:
		return 2.0
	case MonsterTypeOrc:
		return 1.2
	case MonsterTypeDragon:
		return 0.8
	default:
		return 1.5
	}
}

// getRandomEdgePosition 获取随机边缘位置
func (g *Game) getRandomEdgePosition() Position {
	edge := rand.Intn(4)
	switch edge {
	case 0: // 上边
		return Position{X: rand.Float64() * g.config.MapWidth, Y: 0}
	case 1: // 右边
		return Position{X: g.config.MapWidth, Y: rand.Float64() * g.config.MapHeight}
	case 2: // 下边
		return Position{X: rand.Float64() * g.config.MapWidth, Y: g.config.MapHeight}
	case 3: // 左边
		return Position{X: 0, Y: rand.Float64() * g.config.MapHeight}
	default:
		return Position{X: 0, Y: 0}
	}
}

// Update 更新游戏状态(供外部手动调用)
func (g *Game) Update() {
	g.updateInternal()
}

// updateInternal 内部更新游戏状态(由定时器自动调用)
func (g *Game) updateInternal() {
	g.mu.Lock()
	defer g.mu.Unlock()

	if g.gameOver {
		return
	}

	// 移动所有怪物向玩家
	for _, monster := range g.monsters {
		g.moveMonster(monster)
	}

	// 更新子弹
	g.updateBullets()

	// 更新范围效果
	g.updateAreaEffects()

	// 检测碰撞和攻击
	g.checkCombat()
}

// updateBullets 更新所有子弹
func (g *Game) updateBullets() {
	// 记录需要删除的子弹
	toRemove := make([]string, 0)

	for id, bullet := range g.bullets {
		if !bullet.Active {
			toRemove = append(toRemove, id)
			continue
		}

		// 移动子弹
		bullet.Position.X += bullet.Direction.X * bullet.Speed
		bullet.Position.Y += bullet.Direction.Y * bullet.Speed

		// 检测是否超出地图边界
		if bullet.Position.X < 0 || bullet.Position.X > g.config.MapWidth ||
			bullet.Position.Y < 0 || bullet.Position.Y > g.config.MapHeight {
			toRemove = append(toRemove, id)
			continue
		}

		// 检测子弹与怪物碰撞
		for monsterID, monster := range g.monsters {
			if distance := bullet.Position.Distance(monster.Position); distance <= 40 { // 子弹碰撞半径增加至40,防止高速子弹跳过碰撞
				monster.HP -= bullet.Damage
				if bullet.Critical {
					log.Printf("暴击!子弹 %s 击中怪物 %s,造成 %d 点暴击伤害,剩余血量: %d", bullet.ID, monsterID, bullet.Damage, monster.HP)
				} else {
					log.Printf("子弹 %s 击中怪物 %s,造成 %d 点伤害,剩余血量: %d", bullet.ID, monsterID, bullet.Damage, monster.HP)
				}

				// 击退效果:怪物被子弹击退
				if bullet.Knockback > 0 {
					// 计算击退后的位置
					knockbackX := monster.Position.X + bullet.Direction.X*bullet.Knockback
					knockbackY := monster.Position.Y + bullet.Direction.Y*bullet.Knockback

					// 限制在地图边界内
					if knockbackX < 0 {
						knockbackX = 0
					}
					if knockbackX > g.config.MapWidth {
						knockbackX = g.config.MapWidth
					}
					if knockbackY < 0 {
						knockbackY = 0
					}
					if knockbackY > g.config.MapHeight {
						knockbackY = g.config.MapHeight
					}

					monster.Position.X = knockbackX
					monster.Position.Y = knockbackY
					log.Printf("怪物 %s 被击退至 (%.2f, %.2f)", monsterID, knockbackX, knockbackY)
				}

				if monster.HP <= 0 {
					delete(g.monsters, monsterID)
					log.Printf("怪物 %s 被击杀", monsterID)
				}

				// 穿透子弹命中后继续穿透直到穿透次数用完
				// 非穿透子弹命中后直接结束
				if !bullet.Penetration {
					break
				} else {
					bullet.RemainingPenetrations--
					log.Printf("子弹 %s 剩余穿透次数: %d", bullet.ID, bullet.RemainingPenetrations)
					// 穿透次数用完,子弹消失
					if bullet.RemainingPenetrations <= 0 {
						bullet.Active = false
						toRemove = append(toRemove, id)
						break
					}
				}
			}
		}

		// 穿透子弹没有射程限制,非穿透子弹检查射程
		// 注意:穿透子弹的穿透次数检查在碰撞循环内完成
	}

	// 删除无效子弹
	for _, id := range toRemove {
		delete(g.bullets, id)
	}
}

// updateAreaEffects 更新所有范围效果
func (g *Game) updateAreaEffects() {
	// 记录需要删除的效果
	toRemove := make([]string, 0)

	for id, effect := range g.areaEffects {
		if !effect.Active {
			toRemove = append(toRemove, id)
			continue
		}

		// 更新已过时间
		effect.Elapsed += g.config.UpdateInterval

		// 检查是否过期
		if effect.Elapsed >= effect.Duration {
			effect.Active = false
			toRemove = append(toRemove, id)
			continue
		}

		// 检测范围内怪物并造成伤害
		if effect.Elapsed-effect.LastTick >= effect.TickInterval {
			// 获取对应的效果策略
			strategy := GetEffectStrategy(effect.Type)

			// 遍历所有怪物
			for _, monster := range g.monsters {
				distance := effect.Position.Distance(monster.Position)
				if distance <= effect.Radius {
					// 冰冻效果特殊处理:减速
					if effect.Type == EffectTypeIce {
						// 只有当怪物当前没有减速效果或减速效果即将过期时才更新
						if monster.SpeedReduce <= 0 || time.Now().After(monster.SpeedReduceEnd) {
							// 应用减速效果
							monster.SpeedReduce = effect.SpeedReduce
							monster.CurrentSpeed = monster.Speed * (1 - effect.SpeedReduce)
							monster.SpeedReduceEnd = time.Now().Add(effect.Duration)
							log.Printf("怪物 %s 被减速 %.0f%%,当前速度: %.2f",
								monster.ID, effect.SpeedReduce*100, monster.CurrentSpeed)
						}
					} else if effect.Type == EffectTypeStun {
						// 眩晕效果:怪物定身
						if !monster.Stunned || time.Now().After(monster.StunnedEnd) {
							monster.Stunned = true
							monster.StunnedEnd = time.Now().Add(effect.Duration)
							log.Printf("怪物 %s 被眩晕,持续时间: %v", monster.ID, effect.Duration)
						}
					} else if effect.Type == EffectTypeBurn {
						// 燃烧效果:持续挂载范围内的怪物
						if !monster.Burning {
							monster.Burning = true
							monster.BurnEnd = time.Now().Add(effect.Duration)
							monster.BurnDamage = effect.Damage
							log.Printf("怪物 %s 被挂载燃烧效果,每秒伤害: %d", monster.ID, effect.Damage)
						}
					} else {
						// 其他效果(火焰、毒)造成伤害
						isDead := strategy.OnTick(effect, monster, g)
						if isDead {
							log.Printf("怪物 %s 被 %s 效果击杀", monster.ID, strategy.GetName())
							delete(g.monsters, monster.ID)
						}
					}
				}
			}
			effect.LastTick = effect.Elapsed
		}
	}

	// 删除无效效果
	for _, id := range toRemove {
		delete(g.areaEffects, id)
	}
}

// moveMonster 移动怪物向玩家
func (g *Game) moveMonster(monster *Monster) {
	now := time.Now()

	// 检查眩晕状态
	if monster.Stunned && now.Before(monster.StunnedEnd) {
		// 怪物被眩晕,无法移动
		return
	}
	// 眩晕效果结束
	if monster.Stunned && now.After(monster.StunnedEnd) {
		monster.Stunned = false
		log.Printf("怪物 %s 眩晕效果结束", monster.ID)
	}

	// 检查燃烧状态
	if monster.Burning && now.Before(monster.BurnEnd) {
		// 燃烧伤害(每秒扣除)
		monster.HP -= monster.BurnDamage
		if monster.HP <= 0 {
			log.Printf("怪物 %s 燃烧伤害死亡,剩余血量: %d", monster.ID, monster.HP)
			return
		}
	}
	// 燃烧效果结束
	if monster.Burning && now.After(monster.BurnEnd) {
		monster.Burning = false
		monster.BurnDamage = 0
		log.Printf("怪物 %s 燃烧效果结束", monster.ID)
	}

	// 更新当前速度(检查减速效果是否过期)
	if monster.SpeedReduce > 0 && now.After(monster.SpeedReduceEnd) {
		// 减速效果结束,恢复原始速度
		monster.SpeedReduce = 0
		monster.CurrentSpeed = monster.Speed
		log.Printf("怪物 %s 减速效果结束", monster.ID)
	}

	// 计算方向
	dx := g.player.Position.X - monster.Position.X
	dy := g.player.Position.Y - monster.Position.Y
	distance := math.Sqrt(dx*dx + dy*dy)

	if distance > 0 {
		// 标准化方向向量
		dx /= distance
		dy /= distance

		// 使用当前速度移动
		monster.Position.X += dx * monster.CurrentSpeed
		monster.Position.Y += dy * monster.CurrentSpeed
	}
}

// checkCombat 检测战斗
func (g *Game) checkCombat() {
	// 玩家攻击怪物
	for id, monster := range g.monsters {
		distance := g.player.Position.Distance(monster.Position)
		if distance <= g.config.AttackRange {
			monster.HP -= g.player.Attack
			log.Printf("monster %s HP: %d", id, monster.HP)
			if monster.HP <= 0 {
				log.Printf("monster %s is defeated", id)
				delete(g.monsters, id)
			}
		}
	}

	// 怪物攻击玩家
	for _, monster := range g.monsters {
		distance := g.player.Position.Distance(monster.Position)
		if distance <= g.config.AttackRange {
			g.player.HP -= 5 // 怪物基础伤害
			log.Printf("player %s HP: %d", g.player.ID, g.player.HP)
			if g.player.HP <= 0 {
				log.Printf("player %s is defeated", g.player.ID)
				g.gameOver = true
			}
		}
	}
}

// IsGameOver 游戏是否结束
func (g *Game) IsGameOver() bool {
	g.mu.RLock()
	defer g.mu.RUnlock()
	return g.gameOver
}

// GetConfig 获取游戏配置
func (g *Game) GetConfig() GameConfig {
	return g.config
}

测试

package game

import (
	"math"
	"testing"
	"time"
)

func TestNewGame(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 1 * time.Second,
		UpdateInterval:       100 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        100,
	}

	game := NewGame(config)

	if game.GetPlayer().Position.X != config.MapWidth/2 || game.GetPlayer().Position.Y != config.MapHeight/2 {
		t.Errorf("玩家初始位置不正确,期望(%f, %f),实际(%f, %f)",
			config.MapWidth/2, config.MapHeight/2,
			game.GetPlayer().Position.X, game.GetPlayer().Position.Y)
	}

	if game.GetPlayer().HP != config.PlayerStartHP {
		t.Errorf("玩家初始血量不正确,期望%d,实际%d", config.PlayerStartHP, game.GetPlayer().HP)
	}
}

func TestMonsterSpawning(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       100 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        100,
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(350 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) < 3 {
		t.Errorf("怪物生成数量不足,期望至少3个,实际%d个", len(monsters))
	}

	// 检查怪物类型
	for _, monster := range monsters {
		switch monster.Type {
		case MonsterTypeSlime, MonsterTypeGoblin, MonsterTypeOrc, MonsterTypeDragon:
			// 正常类型
		default:
			t.Errorf("未知怪物类型: %s", monster.Type)
		}
	}
}

func TestMonsterMovement(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       100 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000, // 高血量防止死亡
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(150 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}

	// 记录初始位置
	initialPos := monsters[0].Position

	// 更新游戏状态
	game.Update()

	// 检查怪物是否移动
	newPos := monsters[0].Position
	if initialPos.X == newPos.X && initialPos.Y == newPos.Y {
		t.Error("怪物没有移动")
	}
}

func TestCombat(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 50 * time.Millisecond, // 更快生成怪物
		UpdateInterval:       100 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成更多怪物
	time.Sleep(300 * time.Millisecond)

	// 获取怪物数量
	initialMonsterCount := len(game.GetMonsters())
	t.Logf("怪物数量: %d", initialMonsterCount)
	if initialMonsterCount == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}

	// 设置怪物速度为快速
	g := game.GetMonsters()
	for _, m := range g {
		m.Speed = 100.0
	}

	// 多次更新让怪物靠近玩家并战斗
	for i := 0; i < 1000; i++ {
		game.Update()
	}

	// 检查怪物是否被击杀
	finalMonsterCount := len(game.GetMonsters())
	if finalMonsterCount >= initialMonsterCount {
		t.Errorf("怪物没有被击杀,初始%d个,最终%d个", initialMonsterCount, finalMonsterCount)
	}
}

func TestGameOver(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 50 * time.Millisecond,
		UpdateInterval:       100 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        10,
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成多个怪物
	time.Sleep(200 * time.Millisecond)

	// 设置怪物速度为快速
	g := game.GetMonsters()
	if len(g) < 2 {
		t.Skip("怪物数量不足,跳过测试")
	}
	for _, m := range g {
		m.Speed = 100.0
	}

	// 更新直到游戏结束
	for i := 0; i < 50; i++ {
		game.Update()
		if game.IsGameOver() {
			break
		}
	}

	if !game.IsGameOver() {
		t.Error("游戏应该结束但没有结束")
	}

	if game.GetPlayer().HP > 0 {
		t.Errorf("玩家应该死亡,当前血量%d", game.GetPlayer().HP)
	}
}

func TestAutoShoot(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
		AutoShootRange:       200,                    // 设置自动射击范围
		AutoShootInterval:    200 * time.Millisecond, // 设置自动射击间隔
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(200 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}

	// 获取一个怪物并设置它在自动射击范围内(较近位置,让子弹更快到达)
	monster := monsters[0]
	playerPos := game.GetPlayer().Position
	monster.Position = Position{X: playerPos.X + 50, Y: playerPos.Y} // 在200范围内,距离50
	monster.Speed = 0                                                // 禁止移动
	monster.HP = 50

	t.Logf("玩家位置: (%f, %f)", playerPos.X, playerPos.Y)
	t.Logf("怪物位置: (%f, %f), 血量: %d", monster.Position.X, monster.Position.Y, monster.HP)

	// 等待自动射击并更新游戏状态
	time.Sleep(500 * time.Millisecond)
	// 额外调用几次更新确保子弹能到达目标
	for i := 0; i < 10; i++ {
		game.Update()
	}

	// 检查怪物是否被自动射击击中
	if monster.HP >= 50 {
		t.Error("怪物没有被自动射击击中")
	} else {
		t.Logf("怪物被自动射击击中,剩余血量: %d", monster.HP)
	}
}

func TestShooting(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(200 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}

	// 获取一个怪物并设置它在玩家正前方
	monster := monsters[0]
	playerPos := game.GetPlayer().Position
	monster.Position = Position{X: playerPos.X + 100, Y: playerPos.Y}
	monster.Speed = 0 // 禁止移动
	monster.HP = 50

	t.Logf("玩家位置: (%f, %f)", playerPos.X, playerPos.Y)
	t.Logf("怪物位置: (%f, %f), 血量: %d", monster.Position.X, monster.Position.Y, monster.HP)

	// 向怪物方向射击
	game.Shoot(monster.Position.X, monster.Position.Y)

	// 更新游戏状态让子弹飞行
	for i := 0; i < 20; i++ {
		game.Update()
	}

	// 检查怪物是否被击中
	if monster.HP >= 50 {
		t.Error("怪物没有被击中")
	} else {
		t.Logf("怪物被击中,剩余血量: %d", monster.HP)
	}
}

func TestFireEffect(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(200 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}

	// 获取一个怪物并设置它在火海范围内
	monster := monsters[0]
	monster.Position = Position{X: 500, Y: 500} // 火海中心
	monster.Speed = 0                           // 禁止移动
	monster.HP = 50

	t.Logf("怪物位置: (%f, %f), 血量: %d", monster.Position.X, monster.Position.Y, monster.HP)

	// 创建火海(持续2秒,范围100,每500ms造成5点伤害)
	game.CreateFireEffect(500, 500, 100, 5, 2*time.Second)

	// 等待火海造成伤害(等待4次伤害tick)
	time.Sleep(2500 * time.Millisecond)

	// 检查怪物是否被火海击杀
	if monster.HP > 0 {
		t.Errorf("火海应该击杀怪物,当前血量: %d", monster.HP)
	} else {
		t.Logf("火海测试完成,怪物被击杀")
	}
}

func TestPoisonEffect(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(200 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}

	// 设置多个怪物在毒池范围内
	playerPos := game.GetPlayer().Position
	for i, monster := range monsters {
		monster.Position = Position{X: playerPos.X + float64(i)*20, Y: playerPos.Y}
		monster.Speed = 0
		monster.HP = 30
	}

	// 创建毒池
	game.CreatePoisonEffect(playerPos.X, playerPos.Y, 80, 3, 1*time.Second)

	// 等待毒池造成伤害
	time.Sleep(1500 * time.Millisecond)

	// 检查有多少怪物被毒击杀
	deadCount := 0
	for _, monster := range monsters {
		if monster.HP <= 0 {
			deadCount++
		}
	}

	t.Logf("毒池测试完成,被击杀怪物数量: %d/%d", deadCount, len(monsters))
	if deadCount == 0 {
		t.Error("应该有怪物被毒池击杀")
	}
}

func TestAreaEffectDuration(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 创建一个持续1秒的火海
	game.CreateFireEffect(500, 500, 50, 10, 1*time.Second)

	// 检查效果是否已创建
	if len(game.areaEffects) != 1 {
		t.Errorf("应该有1个活动效果,当前: %d", len(game.areaEffects))
	}

	// 等待效果结束
	time.Sleep(1500 * time.Millisecond)

	// 检查效果是否已自动移除
	if len(game.areaEffects) != 0 {
		t.Errorf("效果应该已自动移除,当前: %d", len(game.areaEffects))
	} else {
		t.Log("范围效果持续时间测试通过")
	}
}

func TestIceEffect(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
		AutoShootRange:       -1, // 关闭自动射击
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(200 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}

	// 设置一个怪物在冰冻范围内
	monster := monsters[0]
	monster.Position = Position{X: 500, Y: 500}
	monster.HP = 1000 // 设置高HP确保怪物不会在测试期间死亡
	originalSpeed := monster.Speed
	t.Logf("怪物原始速度: %.2f", originalSpeed)

	// 创建冰冻效果(减速50%)
	game.CreateIceEffect(500, 500, 100, 0.5, 1*time.Second)

	// 等待减速效果生效
	time.Sleep(200 * time.Millisecond)

	// 检查怪物速度是否降低
	if monster.CurrentSpeed >= originalSpeed {
		t.Errorf("怪物速度应该降低,当前速度: %.2f,原始速度: %.2f", monster.CurrentSpeed, originalSpeed)
	} else {
		t.Logf("冰冻效果生效,怪物速度从 %.2f 降至 %.2f", originalSpeed, monster.CurrentSpeed)
	}

	// 等待减速效果结束
	time.Sleep(1200 * time.Millisecond)

	// 检查速度是否恢复
	if monster.CurrentSpeed != originalSpeed {
		t.Errorf("怪物速度应该恢复,当前: %.2f,原始: %.2f", monster.CurrentSpeed, originalSpeed)
	} else {
		t.Log("减速效果结束,怪物速度已恢复")
	}
}

func TestBulletKnockback(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
		AutoShootRange:       -1, // 关闭自动射击
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(200 * time.Millisecond)

	// 获取怪物ID
	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}
	monsterID := monsters[0].ID

	// 切换为狙击枪(有更大击退效果)
	game.GetPlayer().Weapon = NewWeapon(WeaponTypeSniper, 1)

	// 设置怪物位置(通过游戏内部方法)
	game.mu.Lock()
	if monster, ok := game.monsters[monsterID]; ok {
		monster.Position = Position{X: 700, Y: 500}
		monster.HP = 200
		monster.Speed = 0
		monster.CurrentSpeed = 0
		monster.Stunned = true // 眩晕怪物,防止任何移动
		monster.StunnedEnd = time.Now().Add(10 * time.Second)
	}
	originalPos := game.monsters[monsterID].Position
	t.Logf("怪物原始位置: (%.2f, %.2f)", originalPos.X, originalPos.Y)
	game.mu.Unlock()

	// 射击怪物
	game.Shoot(700, 500)

	// 等待子弹飞行(狙击枪子弹速度快,增加等待时间)
	time.Sleep(500 * time.Millisecond)

	// 检查怪物是否被击退(线程安全方式)
	game.mu.Lock()
	monster := game.monsters[monsterID]
	knockbackDistance := math.Sqrt(math.Pow(monster.Position.X-originalPos.X, 2) + math.Pow(monster.Position.Y-originalPos.Y, 2))
	t.Logf("怪物被击退距离: %.2f,新位置: (%.2f, %.2f)", knockbackDistance, monster.Position.X, monster.Position.Y)
	game.mu.Unlock()

	// 狙击枪击退力为50,应该有明显的击退效果
	if knockbackDistance < 30 {
		t.Errorf("狙击枪击退效果不足,实际距离: %.2f", knockbackDistance)
	} else {
		t.Log("狙击枪击退效果测试通过")
	}
}

func TestShotgun(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
	}

	game := NewGame(config)
	// 切换为xian弹枪
	game.GetPlayer().Weapon = NewWeapon(WeaponTypeShotgun, 1)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(300 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}

	// 设置多个怪物在xian弹枪范围内
	playerPos := game.GetPlayer().Position
	for i, monster := range monsters {
		// 将怪物排列成扇形
		angle := (float64(i) - float64(len(monsters)-1)/2) * 0.2
		monster.Position = Position{
			X: playerPos.X + 80*math.Cos(angle),
			Y: playerPos.Y + 80*math.Sin(angle),
		}
		monster.Speed = 0
		monster.HP = 50
		t.Logf("怪物%d位置: (%f, %f)", i, monster.Position.X, monster.Position.Y)
	}

	// 向中间怪物射击
	if len(monsters) > 0 {
		targetMonster := monsters[len(monsters)/2]
		game.Shoot(targetMonster.Position.X, targetMonster.Position.Y)
	}

	// 等待子弹命中
	for i := 0; i < 10; i++ {
		game.Update()
	}

	// 检查有多少怪物被击中
	hitCount := 0
	for _, monster := range monsters {
		if monster.HP < 50 {
			hitCount++
			t.Logf("怪物被击中,剩余血量: %d", monster.HP)
		}
	}

	t.Logf("xian弹枪测试完成,击中怪物数量: %d/%d", hitCount, len(monsters))
	if hitCount == 0 {
		t.Error("没有怪物被xian弹枪击中")
	}
}

func TestRifle(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
	}

	game := NewGame(config)
	// 切换为步枪(带穿透)
	game.GetPlayer().Weapon = NewWeapon(WeaponTypeRifle, 2)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(300 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) < 2 {
		t.Skip("怪物数量不足,跳过测试")
	}

	// 设置怪物排成一条直线
	playerPos := game.GetPlayer().Position
	monsters[0].Position = Position{X: playerPos.X + 100, Y: playerPos.Y}
	monsters[1].Position = Position{X: playerPos.X + 250, Y: playerPos.Y}
	monsters[0].Speed = 0
	monsters[1].Speed = 0
	monsters[0].HP = 50
	monsters[1].HP = 50

	t.Logf("怪物1位置: (%f, %f)", monsters[0].Position.X, monsters[0].Position.Y)
	t.Logf("怪物2位置: (%f, %f)", monsters[1].Position.X, monsters[1].Position.Y)

	// 向远处怪物射击
	game.Shoot(monsters[1].Position.X, monsters[1].Position.Y)

	// 等待子弹穿透
	for i := 0; i < 20; i++ {
		game.Update()
	}

	// 检查两个怪物是否都被击中
	if monsters[0].HP >= 50 {
		t.Error("怪物1没有被步枪穿透击中")
	}
	if monsters[1].HP >= 50 {
		t.Error("怪物2没有被步枪击中")
	}

	t.Logf("步枪测试完成 - 怪物1剩余血量: %d, 怪物2剩余血量: %d", monsters[0].HP, monsters[1].HP)
}

func TestSniper(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
	}

	game := NewGame(config)
	// 切换为高等级狙击枪
	game.GetPlayer().Weapon = NewWeapon(WeaponTypeSniper, 3)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(200 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}

	// 设置怪物在远距离
	monster := monsters[0]
	playerPos := game.GetPlayer().Position
	monster.Position = Position{X: playerPos.X + 400, Y: playerPos.Y} // 远距离
	monster.Speed = 0
	monster.HP = 100

	t.Logf("怪物位置: (%f, %f), 初始血量: %d", monster.Position.X, monster.Position.Y, monster.HP)

	// 狙击枪射击
	game.Shoot(monster.Position.X, monster.Position.Y)

	// 等待子弹飞行
	for i := 0; i < 30; i++ {
		game.Update()
	}

	// 检查高伤害
	weapon := game.GetPlayer().Weapon
	expectedDamage := int(float64(weapon.Damage) * weapon.DamageMultiplier)
	t.Logf("狙击枪伤害: 基础%d × 倍率%.2f = %d", weapon.Damage, weapon.DamageMultiplier, expectedDamage)

	if monster.HP > 100-expectedDamage {
		t.Errorf("狙击枪伤害不足,期望至少%d,实际造成%d", expectedDamage, 100-monster.HP)
	} else {
		t.Logf("狙击枪测试完成,怪物剩余血量: %d", monster.HP)
	}
}

func TestWeaponLevel(t *testing.T) {
	// 测试不同等级武器的伤害倍率
	weapon1 := NewWeapon(WeaponTypePistol, 1)
	weapon5 := NewWeapon(WeaponTypePistol, 5)

	t.Logf("1级手枪: 伤害%d, 倍率%.2f, 最终伤害%d",
		weapon1.Damage, weapon1.DamageMultiplier,
		int(float64(weapon1.Damage)*weapon1.DamageMultiplier))
	t.Logf("5级手枪: 伤害%d, 倍率%.2f, 最终伤害%d",
		weapon5.Damage, weapon5.DamageMultiplier,
		int(float64(weapon5.Damage)*weapon5.DamageMultiplier))

	// 验证伤害倍率计算正确
	expectedMultiplier := 1.0 + float64(5-1)*0.2 // 1.8
	if math.Abs(weapon5.DamageMultiplier-expectedMultiplier) > 0.01 {
		t.Errorf("5级武器伤害倍率计算错误,期望%.2f,实际%.2f", expectedMultiplier, weapon5.DamageMultiplier)
	}
}

func TestBulletPenetration(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 50 * time.Millisecond, // 更快生成
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(300 * time.Millisecond) // 更长等待时间

	monsters := game.GetMonsters()
	if len(monsters) < 2 {
		t.Skip("怪物数量不足,跳过测试")
	}

	// 获取玩家位置
	playerPos := game.GetPlayer().Position

	// 设置两个怪物在同一条线上
	monster1 := monsters[0]
	monster2 := monsters[1]
	monster1.Position = Position{X: playerPos.X + 50, Y: playerPos.Y}
	monster2.Position = Position{X: playerPos.X + 150, Y: playerPos.Y}
	monster1.Speed = 0
	monster2.Speed = 0
	monster1.HP = 20
	monster2.HP = 20

	t.Logf("怪物1位置: (%f, %f), 血量: %d", monster1.Position.X, monster1.Position.Y, monster1.HP)
	t.Logf("怪物2位置: (%f, %f), 血量: %d", monster2.Position.X, monster2.Position.Y, monster2.HP)

	// 设置武器穿透次数为2
	game.GetPlayer().Weapon.MaxPenetration = 2
	// 开启穿透功能
	game.ToggleWeaponPenetration()

	// 向怪物方向射击
	game.Shoot(monster2.Position.X, monster2.Position.Y)

	// 更新游戏状态让子弹飞行
	for i := 0; i < 30; i++ {
		game.Update()
	}

	// 检查两个怪物是否都被击中
	if monster1.HP >= 20 {
		t.Error("怪物1没有被穿透子弹击中")
	}
	if monster2.HP >= 20 {
		t.Error("怪物2没有被击中")
	}

	t.Logf("穿透测试完成 - 怪物1剩余血量: %d, 怪物2剩余血量: %d", monster1.HP, monster2.HP)
}

func TestMonsterFollowsPlayer(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       100 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(150 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}

	// 设置怪物速度为慢速,方便观察
	for _, m := range monsters {
		m.Speed = 5.0
	}

	// 设置怪物初始位置在玩家上方(Y坐标小于玩家)
	game.mu.Lock()
	monsterID := monsters[0].ID
	if monster, ok := game.monsters[monsterID]; ok {
		monster.Position = Position{X: 500, Y: 300} // 在玩家上方
	}
	monster := game.monsters[monsterID]
	initialMonsterPos := monster.Position
	initialPlayerPos := game.player.Position
	game.mu.Unlock()

	t.Logf("初始状态 - 玩家位置: (%f, %f), 怪物位置: (%f, %f)",
		initialPlayerPos.X, initialPlayerPos.Y,
		initialMonsterPos.X, initialMonsterPos.Y)

	// 更新几次让怪物开始移动
	for i := 0; i < 5; i++ {
		game.Update()
	}

	// 记录怪物向初始位置移动后的位置
	game.mu.Lock()
	posAfterFirstMove := game.monsters[monsterID].Position
	game.mu.Unlock()
	t.Logf("怪物向初始玩家位置移动后: (%f, %f)", posAfterFirstMove.X, posAfterFirstMove.Y)

	// 玩家移动到新位置
	game.MovePlayer(100, 100)
	newPlayerPos := game.GetPlayer().Position
	t.Logf("玩家移动到新位置: (%f, %f)", newPlayerPos.X, newPlayerPos.Y)

	// 更新几次让怪物改变方向
	for i := 0; i < 5; i++ {
		game.Update()
	}

	// 检查怪物是否改变了移动方向(朝向新的玩家位置)
	game.mu.Lock()
	posAfterPlayerMove := game.monsters[monsterID].Position
	game.mu.Unlock()
	t.Logf("怪物向新玩家位置移动后: (%f, %f)", posAfterPlayerMove.X, posAfterPlayerMove.Y)

	// 计算怪物移动方向
	// 如果玩家向右移动,怪物应该也向右移动更多
	if newPlayerPos.X > initialPlayerPos.X {
		if posAfterPlayerMove.X <= posAfterFirstMove.X {
			t.Error("怪物应该跟随玩家向右移动")
		}
	}

	if newPlayerPos.Y > initialPlayerPos.Y {
		if posAfterPlayerMove.Y <= posAfterFirstMove.Y {
			t.Error("怪物应该跟随玩家向下移动")
		}
	}

	t.Log("怪物成功跟随玩家移动!")
}

func TestPositionDistance(t *testing.T) {
	p1 := Position{X: 0, Y: 0}
	p2 := Position{X: 3, Y: 4}

	distance := p1.Distance(p2)
	if distance != 5 {
		t.Errorf("距离计算错误,期望5,实际%f", distance)
	}
}

func TestMonsterHP(t *testing.T) {
	tests := []struct {
		name     string
		monster  MonsterType
		expected int
	}{
		{"史莱姆", MonsterTypeSlime, 20},
		{"哥布林", MonsterTypeGoblin, 40},
		{"兽人", MonsterTypeOrc, 80},
		{"龙", MonsterTypeDragon, 200},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			hp := getMonsterHP(tt.monster)
			if hp != tt.expected {
				t.Errorf("%s血量错误,期望%d,实际%d", tt.name, tt.expected, hp)
			}
		})
	}
}

// TestStunEffect 测试眩晕效果
func TestStunEffect(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
		AutoShootRange:       -1, // 关闭自动射击
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(200 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}
	monsterID := monsters[0].ID

	// 设置怪物位置在玩家附近,并立即眩晕(线程安全方式)
	game.mu.Lock()
	var initialPos Position
	if monster, ok := game.monsters[monsterID]; ok {
		monster.Position = Position{X: 550, Y: 500}
		monster.HP = 200
		// 立即设置眩晕状态
		monster.Stunned = true
		monster.StunnedEnd = time.Now().Add(2 * time.Second)
		initialPos = monster.Position
	}
	game.mu.Unlock()

	// 等待一段时间,让游戏更新
	time.Sleep(300 * time.Millisecond)

	// 更新游戏状态
	game.Update()

	// 检查怪物是否被眩晕(应该没有移动)
	game.mu.Lock()
	monster := game.monsters[monsterID]
	newPos := monster.Position
	isStunned := monster.Stunned
	game.mu.Unlock()

	if !isStunned {
		t.Error("怪物应该被眩晕")
	}

	// 检查怪物位置是否变化(眩晕期间应该不移动)
	if math.Abs(newPos.X-initialPos.X) > 1 || math.Abs(newPos.Y-initialPos.Y) > 1 {
		t.Errorf("眩晕期间怪物不应该移动,初始位置: (%.2f, %.2f),新位置: (%.2f, %.2f)",
			initialPos.X, initialPos.Y, newPos.X, newPos.Y)
	}

	t.Log("眩晕效果测试通过")
}

// TestBurnEffect 测试燃烧效果
func TestBurnEffect(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
		AutoShootRange:       -1, // 关闭自动射击
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(200 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}
	monsterID := monsters[0].ID

	// 设置怪物位置和血量
	game.mu.Lock()
	if monster, ok := game.monsters[monsterID]; ok {
		monster.Position = Position{X: 600, Y: 500}
		monster.HP = 100
	}
	initialHP := game.monsters[monsterID].HP
	game.mu.Unlock()

	t.Logf("怪物初始血量: %d", initialHP)

	// 创建燃烧效果(每秒伤害10点,持续2秒)
	game.CreateBurnEffect(600, 500, 80, 10, 2*time.Second)

	// 等待燃烧效果生效
	time.Sleep(600 * time.Millisecond)

	// 检查怪物是否被燃烧
	game.mu.Lock()
	monster := game.monsters[monsterID]
	isBurning := monster.Burning
	currentHP := monster.HP
	game.mu.Unlock()

	if !isBurning {
		t.Error("怪物应该被燃烧")
	}

	// 检查燃烧伤害(应该至少扣除一次伤害)
	if currentHP >= initialHP {
		t.Errorf("燃烧应该造成伤害,初始血量: %d,当前血量: %d", initialHP, currentHP)
	}

	t.Logf("燃烧效果测试通过,怪物剩余血量: %d", currentHP)
}

// TestCriticalHit 测试暴击系统
func TestCriticalHit(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
		AutoShootRange:       -1, // 关闭自动射击
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 等待生成怪物
	time.Sleep(200 * time.Millisecond)

	monsters := game.GetMonsters()
	if len(monsters) == 0 {
		t.Skip("没有生成怪物,跳过测试")
	}
	monsterID := monsters[0].ID

	// 设置怪物位置和高血量
	game.mu.Lock()
	if monster, ok := game.monsters[monsterID]; ok {
		monster.Position = Position{X: 700, Y: 500}
		monster.HP = 500
		monster.Stunned = true
		monster.StunnedEnd = time.Now().Add(10 * time.Second)
	}
	initialHP := game.monsters[monsterID].HP
	game.mu.Unlock()

	t.Logf("怪物初始血量: %d", initialHP)

	// 切换为狙击枪(高暴击率30%)
	game.GetPlayer().Weapon = NewWeapon(WeaponTypeSniper, 1)

	// 多次射击,期望至少出现一次暴击
	totalShoots := 20

	for i := 0; i < totalShoots; i++ {
		game.Shoot(700, 500)
		time.Sleep(100 * time.Millisecond)
	}

	// 等待子弹命中
	time.Sleep(500 * time.Millisecond)

	// 检查怪物剩余血量
	game.mu.Lock()
	monster := game.monsters[monsterID]
	currentHP := monster.HP
	game.mu.Unlock()

	// 计算平均伤害
	baseDamage := 50 // 狙击枪基础伤害
	expectedMaxNormalDamage := baseDamage * totalShoots
	actualDamage := initialHP - currentHP

	t.Logf("总伤害: %d,期望最大普通伤害: %d", actualDamage, expectedMaxNormalDamage)

	// 如果总伤害超过最大普通伤害,说明发生了暴击
	if actualDamage > expectedMaxNormalDamage {
		t.Log("暴击系统测试通过 - 检测到暴击")
	} else {
		// 20次射击30%暴击率,不暴击概率约为0.7^20 ≈ 0.0008,很小概率事件
		t.Logf("未检测到暴击(概率极低),总伤害: %d,期望最大普通伤害: %d", actualDamage, expectedMaxNormalDamage)
	}
}

// TestAmmoSystem 测试弹药系统
func TestAmmoSystem(t *testing.T) {
	config := GameConfig{
		MapWidth:             1000,
		MapHeight:            1000,
		MonsterSpawnInterval: 100 * time.Millisecond,
		UpdateInterval:       50 * time.Millisecond,
		AttackRange:          50,
		PlayerStartHP:        1000,
		AutoShootRange:       -1, // 关闭自动射击
	}

	game := NewGame(config)
	game.Start()
	defer game.Stop()

	// 切换为手枪(12发弹药,500ms射速冷却)
	pistol := NewWeapon(WeaponTypePistol, 1)
	game.GetPlayer().Weapon = pistol

	// 检查初始弹药
	if pistol.CurrentAmmo != pistol.MaxAmmo {
		t.Errorf("初始弹药错误,期望%d,实际%d", pistol.MaxAmmo, pistol.CurrentAmmo)
	}

	// 射击12次,消耗所有弹药(等待射速冷却)
	for i := 0; i < 12; i++ {
		game.Shoot(500, 300)
		time.Sleep(pistol.FireRate + 50*time.Millisecond)
	}

	// 检查弹药是否耗尽(线程安全方式)
	game.mu.Lock()
	currentAmmo := pistol.CurrentAmmo
	game.mu.Unlock()

	if currentAmmo != 0 {
		t.Errorf("弹药应该耗尽,当前弹药: %d", currentAmmo)
	}

	// 再次射击,应该自动换弹
	game.Shoot(500, 300)

	// 等待一小段时间确保换弹开始
	time.Sleep(100 * time.Millisecond)

	// 检查是否正在换弹(线程安全方式)
	game.mu.Lock()
	isReloading := game.reloading[string(pistol.Type)]
	game.mu.Unlock()

	if !isReloading {
		t.Error("应该正在换弹")
	}

	// 等待换弹完成
	time.Sleep(pistol.ReloadTime + 200*time.Millisecond)

	// 检查弹药是否恢复
	if pistol.CurrentAmmo != pistol.MaxAmmo {
		t.Errorf("换弹完成后弹药应该恢复,当前弹药: %d/%d", pistol.CurrentAmmo, pistol.MaxAmmo)
	}

	t.Log("弹药系统测试通过")
}
posted on 2026-05-21 11:45  朝阳1  阅读(31)  评论(0)    收藏  举报