From 4217defc010f39e53e59122b4caface150ce0aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=94=E5=BF=B5?= <1@72wo.cn> Date: Mon, 11 Aug 2025 21:16:25 +0800 Subject: [PATCH] 1 --- logic/service/fight/fight.go1 | 1075 ----------------------------- logic/service/fight/fight.md | 51 ++ logic/service/fight/fight_test.go | 368 ++++++++++ logic/service/fight/fsm_test.go | 36 - 4 files changed, 419 insertions(+), 1111 deletions(-) delete mode 100644 logic/service/fight/fight.go1 create mode 100644 logic/service/fight/fight.md create mode 100644 logic/service/fight/fight_test.go delete mode 100644 logic/service/fight/fsm_test.go diff --git a/logic/service/fight/fight.go1 b/logic/service/fight/fight.go1 deleted file mode 100644 index ead258cfb..000000000 --- a/logic/service/fight/fight.go1 +++ /dev/null @@ -1,1075 +0,0 @@ -package fight - -import ( - "bufio" - "fmt" - "math" - "math/rand" - "os" - "strconv" - "strings" - "sync" - "time" -) - -// 战斗阶段 -type BattlePhase int - -const ( - PreTurn BattlePhase = iota // 回合开始前 - PreSkill // 技能使用前 - SkillUse // 技能生效时(执行所有效果) - PostTurn // 回合结束后(处理持续状态) -) - -// 效果接口:所有技能效果都需实现此接口 -type Effect interface { - Execute(ctx *BattleCtx) // 执行效果,修改战斗上下文 -} - -// 技能定义:由多个效果组成 -type Skill struct { - Name string - Type PokemonType // 技能类型 - Effects []Effect // 效果列表(伤害、状态、增益等) - PP int // 技能使用次数 - MaxPP int // 最大使用次数 - Accuracy int // 命中率(0-100) -} - -// 精灵类型 -type PokemonType string - -const ( - TypeElectric PokemonType = "电" - TypeGrass PokemonType = "草" - TypeWater PokemonType = "水" - TypeFire PokemonType = "火" -) - -// 精灵属性 -type Pokemon struct { - ID string - Type PokemonType - HP int - MaxHP int - Attack int - Defense int - SpAttack int - SpDefense int - Speed int - Level int - Skills []*Skill - Statuses []*Status // 持续状态(中毒、烧伤等) - Buffs []*Buff // 临时增益(攻击提升、防御降低等) -} - -// 持续状态(如中毒) -type Status struct { - Name string // 状态名称 - Damage int // 每回合伤害 - Turns int // 剩余持续回合 - CanAct bool // 是否可以行动 - OnTurnEnd func(*Pokemon) // 回合结束时触发的效果 -} - -// 临时增益/减益 -type Buff struct { - Name string // 增益名称 - Stat string // 影响的属性(Attack, Defense等) - Modifier float64 // 修正比例(1.5表示提升50%) - Turns int // 持续回合 -} - -// 玩家接口(统一决策) -type Player interface { - GetID() string - GetPokemons() []*Pokemon - GetActivePokemon() *Pokemon - ChooseSkill(ctx *BattleCtx) *Skill // 选择技能 - ChooseTarget(availableTargets []*Pokemon) *Pokemon // 选择目标 - IsAI() bool -} - -// 战斗上下文:传递阶段数据 + 效果交互 -type BattleCtx struct { - Attacker Player // 进攻方 - Defender Player // 防守方 - User *Pokemon // 当前行动精灵 - Target *Pokemon // 目标精灵 - Damage int // 实时伤害(效果可修改) - Phase BattlePhase // 当前阶段 - Skill *Skill // 当前使用的技能 - IsCrit bool // 是否暴击 -} - -// 战斗管理器:调度阶段 + 效果执行 -type BattleManager struct { - hooks map[BattlePhase][]func(*BattleCtx) // 阶段事件钩子 - isOver bool // 战斗是否结束 - mu sync.Mutex // 线程安全锁 - mode EnumBattleMode // 战斗模式 -} - -func NewBattleManager(mode EnumBattleMode) *BattleManager { - return &BattleManager{ - hooks: make(map[BattlePhase][]func(*BattleCtx)), - mode: mode, - } -} - -// 注册阶段事件 -func (m *BattleManager) On(phase BattlePhase, hook func(*BattleCtx)) { - m.mu.Lock() - defer m.mu.Unlock() - m.hooks[phase] = append(m.hooks[phase], hook) -} - -// 触发阶段事件(支持中断) -func (m *BattleManager) trigger(ctx *BattleCtx) { - m.mu.Lock() - defer m.mu.Unlock() - for _, fn := range m.hooks[ctx.Phase] { - fn(ctx) - if m.isOver { - return // 战斗结束,提前退出 - } - } -} - -// 执行单回合(进攻方 → 防守方) -func (m *BattleManager) RunTurn(attacker, defender Player) { - // 1. 筛选存活精灵 - user := attacker.GetActivePokemon() - if user == nil { - m.isOver = true - fmt.Printf("[%s] 已无存活精灵,战斗结束!\n", attacker.GetID()) - return - } - - // 检查是否可以行动(状态影响) - if !canPokemonAct(user) { - fmt.Printf("[%s] %s 因状态无法行动!\n", attacker.GetID(), user.ID) - return - } - - // 2. 初始化上下文(伤害初始为0,由效果叠加) - ctx := &BattleCtx{ - Attacker: attacker, - Defender: defender, - User: user, - Damage: 0, // 效果将修改此值 - } - - // 3. 阶段1:回合开始前(检查状态、buff) - ctx.Phase = PreTurn - m.trigger(ctx) - if m.isOver { - return - } - - // 4. 决策:选择技能 - availableSkills := getAvailableSkills(user) - if len(availableSkills) == 0 { - fmt.Printf("[%s] %s 无可用技能,跳过本次行动!\n", attacker.GetID(), user.ID) - return - } - - chosenSkill := attacker.ChooseSkill(ctx) - if chosenSkill == nil { - fmt.Printf("[%s] 未选择技能,跳过本次行动!\n", attacker.GetID()) - return - } - ctx.Skill = chosenSkill - - // 5. 选择目标 - availableTargets := getAlivePokemons(defender.GetPokemons()) - if len(availableTargets) == 0 { - m.isOver = true - fmt.Printf("[%s] 已无存活精灵,战斗结束!\n", defender.GetID()) - return - } - - var target *Pokemon - if len(availableTargets) > 1 { - target = attacker.ChooseTarget(availableTargets) - } else { - target = availableTargets[0] - } - ctx.Target = target - - // 6. 阶段2:技能使用前(血量检查、前置条件) - ctx.Phase = PreSkill - m.trigger(ctx) - if m.isOver { - return - } - - // 7. 阶段3:技能生效时(执行所有效果) - ctx.Phase = SkillUse - fmt.Printf("[%s] %s 使用 %s(PP: %d/%d)\n", - attacker.GetID(), user.ID, chosenSkill.Name, chosenSkill.PP, chosenSkill.MaxPP) - - // 检查命中率 - if !checkAccuracy(chosenSkill) { - fmt.Printf(" → 技能 %s 未命中!\n", chosenSkill.Name) - return - } - - // 检查暴击 - ctx.IsCrit = checkCriticalHit() - if ctx.IsCrit { - fmt.Println(" → 暴击!伤害翻倍!") - } - - // 计算属性相克 - typeEffectiveness := calculateTypeEffectiveness(chosenSkill.Type, target.Type) - if typeEffectiveness > 1.0 { - fmt.Println(" → 效果拔群!") - } else if typeEffectiveness < 1.0 && typeEffectiveness > 0 { - fmt.Println(" → 效果一般...") - } else if typeEffectiveness == 0 { - fmt.Println(" → 完全无效!") - return - } - - // 应用属性修正 - modifier := getStatModifier(user, "Attack") / getStatModifier(target, "Defense") - - // 遍历技能的所有效果,依次执行 - for _, effect := range chosenSkill.Effects { - effect.Execute(ctx) - } - - // 应用属性相克和暴击修正 - ctx.Damage = int(float64(ctx.Damage) * typeEffectiveness) - if ctx.IsCrit { - ctx.Damage = int(float64(ctx.Damage) * 2.0) // 暴击伤害翻倍 - } - - // 应用伤害修正 - ctx.Damage = int(float64(ctx.Damage) * modifier) - - m.trigger(ctx) // 触发阶段钩子(可扩展额外逻辑) - if m.isOver { - return - } - - // 8. 应用最终伤害(如果有伤害) - if ctx.Damage > 0 { - target.HP -= ctx.Damage - fmt.Printf(" → 最终伤害:%d,%s 剩余HP: %d\n", - ctx.Damage, target.ID, target.HP) - } - - // 减少技能PP - chosenSkill.PP-- - - // 9. 阶段4:回合结束后(处理持续状态 + 死亡判定) - ctx.Phase = PostTurn - m.trigger(ctx) -} - -// 检查精灵是否可以行动 -func canPokemonAct(p *Pokemon) bool { - for _, status := range p.Statuses { - if !status.CanAct { - return false - } - } - return true -} - -// 获取可用技能 -func getAvailableSkills(p *Pokemon) []*Skill { - var available []*Skill - for _, skill := range p.Skills { - if skill.PP > 0 { - available = append(available, skill) - } - } - return available -} - -// 获取存活精灵 -func getAlivePokemons(pokemons []*Pokemon) []*Pokemon { - var alive []*Pokemon - for _, p := range pokemons { - if p.HP > 0 { - alive = append(alive, p) - } - } - return alive -} - -// 检查命中率 -func checkAccuracy(skill *Skill) bool { - return rand.Intn(100) < skill.Accuracy -} - -// 检查暴击 -func checkCriticalHit() bool { - return rand.Intn(20) == 0 // 5%暴击率 -} - -// 计算属性相克 -func calculateTypeEffectiveness(attackType, defenseType PokemonType) float64 { - // 简单的属性相克表 - effectiveness := map[PokemonType]map[PokemonType]float64{ - TypeElectric: { - TypeWater: 2.0, - TypeGrass: 0.5, - TypeElectric: 0.5, - }, - TypeGrass: { - TypeWater: 2.0, - TypeFire: 2.0, - TypeGrass: 0.5, - TypeElectric: 1.0, - }, - TypeWater: { - TypeFire: 2.0, - TypeGrass: 0.5, - TypeWater: 0.5, - }, - TypeFire: { - TypeGrass: 2.0, - TypeWater: 0.5, - TypeFire: 0.5, - }, - } - - // 默认1.0 - if _, ok := effectiveness[attackType]; !ok { - return 1.0 - } - - if value, ok := effectiveness[attackType][defenseType]; ok { - return value - } - - return 1.0 -} - -// 获取属性修正值 -func getStatModifier(p *Pokemon, stat string) float64 { - modifier := 1.0 - for _, buff := range p.Buffs { - if buff.Stat == stat { - modifier *= buff.Modifier - } - } - return modifier -} - -// -------------------- 效果实现 -------------------- // - -// 1. 基础伤害效果 -type DamageEffect struct { - BasePower int // 基础伤害值 -} - -func (e *DamageEffect) Execute(ctx *BattleCtx) { - // 基础伤害 + 攻击力修正 - attack := ctx.User.Attack - if ctx.Skill.Type == ctx.User.Type { - attack = int(float64(attack) * 1.5) // 本系加成50% - } - - // 简单的伤害计算公式 - baseDamage := int(math.Floor(0.85 * float64(e.BasePower) * float64(attack) / float64(ctx.Target.Defense))) - ctx.Damage += baseDamage -} - -// 2. 伤害修正效果(增伤/减伤) -type ModifyDamageEffect struct { - Percent float64 // 百分比修正(1.5=增伤50%,0.8=减伤20%) -} - -func (e *ModifyDamageEffect) Execute(ctx *BattleCtx) { - ctx.Damage = int(float64(ctx.Damage) * (1 + e.Percent)) -} - -// 3. 附加持续状态(如中毒) -type StatusEffect struct { - Name string // 状态名称 - Damage int // 每回合伤害 - Turns int // 持续回合数 - CanAct bool // 是否可以行动 -} - -func (e *StatusEffect) Execute(ctx *BattleCtx) { - // 检查是否已存在相同状态 - for _, status := range ctx.Target.Statuses { - if status.Name == e.Name { - // 刷新状态 - status.Turns = e.Turns - return - } - } - - ctx.Target.Statuses = append(ctx.Target.Statuses, &Status{ - Name: e.Name, - Damage: e.Damage, - Turns: e.Turns, - CanAct: e.CanAct, - OnTurnEnd: func(p *Pokemon) { - if e.Name == "中毒" { - p.HP -= e.Damage - fmt.Printf(" → %s 因中毒受到 %d 点伤害\n", p.ID, e.Damage) - } else if e.Name == "烧伤" { - p.HP -= e.Damage - fmt.Printf(" → %s 因烧伤受到 %d 点伤害\n", p.ID, e.Damage) - // 烧伤降低攻击力 - p.Attack = int(math.Max(1, float64(p.Attack)*0.75)) - } - }, - }) -} - -// 4. 治疗效果 -type HealEffect struct { - Amount int // 治疗量 -} - -func (e *HealEffect) Execute(ctx *BattleCtx) { - // 治疗量不超过最大HP - healAmount := e.Amount - if ctx.User.HP+healAmount > ctx.User.MaxHP { - healAmount = ctx.User.MaxHP - ctx.User.HP - } - - ctx.User.HP += healAmount - fmt.Printf(" → %s 回复了 %d HP,当前HP: %d\n", - ctx.User.ID, healAmount, ctx.User.HP) -} - -// 5. 属性增益效果 -type StatBoostEffect struct { - Stat string // 影响的属性 - Modifier float64 // 修正比例 - Turns int // 持续回合 -} - -func (e *StatBoostEffect) Execute(ctx *BattleCtx) { - ctx.User.Buffs = append(ctx.User.Buffs, &Buff{ - Name: fmt.Sprintf("%s提升", e.Stat), - Stat: e.Stat, - Modifier: e.Modifier, - Turns: e.Turns, - }) - - fmt.Printf(" → %s的%s提升了!\n", ctx.User.ID, e.Stat) -} - -// 6. 属性减益效果 -type StatDebuffEffect struct { - Stat string // 影响的属性 - Modifier float64 // 修正比例 - Turns int // 持续回合 -} - -func (e *StatDebuffEffect) Execute(ctx *BattleCtx) { - ctx.Target.Buffs = append(ctx.Target.Buffs, &Buff{ - Name: fmt.Sprintf("%s降低", e.Stat), - Stat: e.Stat, - Modifier: 1.0 / e.Modifier, // 减益是增益的倒数 - Turns: e.Turns, - }) - - fmt.Printf(" → %s的%s降低了!\n", ctx.Target.ID, e.Stat) -} - -// -------------------- 玩家实现 -------------------- // - -// 人类玩家 -type HumanPlayer struct { - id string - pokemons []*Pokemon - activeIndex int // 当前活跃精灵的索引 -} - -func NewHumanPlayer(id string, pokemons []*Pokemon) *HumanPlayer { - return &HumanPlayer{ - id: id, - pokemons: pokemons, - activeIndex: 0, // 默认第一只精灵 - } -} - -func (p *HumanPlayer) GetID() string { return p.id } -func (p *HumanPlayer) GetPokemons() []*Pokemon { return p.pokemons } -func (p *HumanPlayer) IsAI() bool { return false } - -func (p *HumanPlayer) GetActivePokemon() *Pokemon { - if p.activeIndex < 0 || p.activeIndex >= len(p.pokemons) { - return nil - } - return p.pokemons[p.activeIndex] -} - -func (p *HumanPlayer) ChooseSkill(ctx *BattleCtx) *Skill { - // 显示当前精灵状态 - fmt.Printf("\n[%s] %s 的回合(HP: %d/%d)\n", - p.id, ctx.User.ID, ctx.User.HP, ctx.User.MaxHP) - - // 显示可用技能 - availableSkills := getAvailableSkills(ctx.User) - if len(availableSkills) == 0 { - fmt.Println("无可用技能!") - return nil - } - - fmt.Println("可用技能:") - for i, skill := range availableSkills { - fmt.Printf("%d. %s (PP: %d/%d)\n", i+1, skill.Name, skill.PP, skill.MaxPP) - } - - // 获取用户选择 - reader := bufio.NewReader(os.Stdin) - var choice int - - for { - fmt.Print("请选择技能 (1-", len(availableSkills), "): ") - input, err := reader.ReadString('\n') - if err != nil { - fmt.Println("输入错误,请重试!") - continue - } - - input = strings.TrimSpace(input) - choice, err = strconv.Atoi(input) - if err != nil || choice < 1 || choice > len(availableSkills) { - fmt.Println("无效选择,请重试!") - continue - } - - break - } - - return availableSkills[choice-1] -} - -func (p *HumanPlayer) ChooseTarget(availableTargets []*Pokemon) *Pokemon { - if len(availableTargets) == 1 { - return availableTargets[0] - } - - fmt.Println("选择目标:") - for i, target := range availableTargets { - fmt.Printf("%d. %s (HP: %d/%d)\n", i+1, target.ID, target.HP, target.MaxHP) - } - - // 获取用户选择 - reader := bufio.NewReader(os.Stdin) - var choice int - - for { - fmt.Print("请选择目标 (1-", len(availableTargets), "): ") - input, err := reader.ReadString('\n') - if err != nil { - fmt.Println("输入错误,请重试!") - continue - } - - input = strings.TrimSpace(input) - choice, err = strconv.Atoi(input) - if err != nil || choice < 1 || choice > len(availableTargets) { - fmt.Println("无效选择,请重试!") - continue - } - - break - } - - return availableTargets[choice-1] -} - -// AI玩家 -type AIPlayer struct { - id string - pokemons []*Pokemon - activeIndex int -} - -func NewAIPlayer(id string, pokemons []*Pokemon) *AIPlayer { - return &AIPlayer{ - id: id, - pokemons: pokemons, - activeIndex: 0, - } -} - -func (p *AIPlayer) GetID() string { return p.id } -func (p *AIPlayer) GetPokemons() []*Pokemon { return p.pokemons } -func (p *AIPlayer) IsAI() bool { return true } - -func (p *AIPlayer) GetActivePokemon() *Pokemon { - if p.activeIndex < 0 || p.activeIndex >= len(p.pokemons) { - return nil - } - return p.pokemons[p.activeIndex] -} - -func (p *AIPlayer) ChooseSkill(ctx *BattleCtx) *Skill { - time.Sleep(1 * time.Second) // 模拟思考 - - availableSkills := getAvailableSkills(ctx.User) - if len(availableSkills) == 0 { - return nil - } - - // 简单AI逻辑:优先选择伤害最高的技能 - bestSkill := availableSkills[0] - highestDamage := 0 - - for _, skill := range availableSkills { - // 计算预估伤害 - estimatedDamage := 0 - for _, effect := range skill.Effects { - if damageEffect, ok := effect.(*DamageEffect); ok { - estimatedDamage += damageEffect.BasePower - } - } - - // 考虑属性相克 - typeEffectiveness := calculateTypeEffectiveness(skill.Type, ctx.Target.Type) - estimatedDamage = int(float64(estimatedDamage) * typeEffectiveness) - - if estimatedDamage > highestDamage { - highestDamage = estimatedDamage - bestSkill = skill - } - } - - fmt.Printf("[%s(AI)] %s 使用 %s\n", p.id, ctx.User.ID, bestSkill.Name) - return bestSkill -} - -func (p *AIPlayer) ChooseTarget(availableTargets []*Pokemon) *Pokemon { - time.Sleep(1 * time.Second) // 模拟思考 - - // 简单AI逻辑:优先选择HP最低的目标 - var target *Pokemon - lowestHP := math.MaxInt32 - - for _, p := range availableTargets { - if p.HP < lowestHP { - lowestHP = p.HP - target = p - } - } - - return target -} - -// -------------------- 示例:创建精灵和技能 -------------------- // - -// 创建皮卡丘 -func createPikachu() *Pokemon { - return &Pokemon{ - ID: "皮卡丘", - Type: TypeElectric, - HP: 100, - MaxHP: 100, - Attack: 55, - Defense: 40, - SpAttack: 50, - SpDefense: 50, - Speed: 90, - Level: 5, - Skills: []*Skill{ - { - Name: "十万伏特", - Type: TypeElectric, - Effects: []Effect{ - &DamageEffect{BasePower: 90}, - }, - PP: 15, - MaxPP: 15, - Accuracy: 100, - }, - { - Name: "伏特攻击", - Type: TypeElectric, - Effects: []Effect{ - &DamageEffect{BasePower: 50}, - &StatusEffect{Name: "麻痹", Damage: 0, Turns: 3, CanAct: false}, - }, - PP: 30, - MaxPP: 30, - Accuracy: 100, - }, - { - Name: "电网", - Type: TypeElectric, - Effects: []Effect{ - &DamageEffect{BasePower: 40}, - &StatDebuffEffect{Stat: "Speed", Modifier: 1.5, Turns: 3}, - }, - PP: 20, - MaxPP: 20, - Accuracy: 100, - }, - { - Name: "充电", - Type: TypeElectric, - Effects: []Effect{ - &HealEffect{Amount: 20}, - &StatBoostEffect{Stat: "SpAttack", Modifier: 1.5, Turns: 2}, - }, - PP: 10, - MaxPP: 10, - Accuracy: 100, - }, - }, - } -} - -// 创建妙蛙种子 -func createBulbasaur() *Pokemon { - return &Pokemon{ - ID: "妙蛙种子", - Type: TypeGrass, - HP: 120, - MaxHP: 120, - Attack: 49, - Defense: 49, - SpAttack: 65, - SpDefense: 65, - Speed: 45, - Level: 5, - Skills: []*Skill{ - { - Name: "飞叶快刀", - Type: TypeGrass, - Effects: []Effect{ - &DamageEffect{BasePower: 55}, - }, - PP: 25, - MaxPP: 25, - Accuracy: 95, - }, - { - Name: "藤鞭", - Type: TypeGrass, - Effects: []Effect{ - &DamageEffect{BasePower: 45}, - }, - PP: 30, - MaxPP: 30, - Accuracy: 100, - }, - { - Name: "寄生种子", - Type: TypeGrass, - Effects: []Effect{ - &StatusEffect{ - Name: "寄生", - Damage: 10, - Turns: 5, - CanAct: true, - }, - }, - PP: 10, - MaxPP: 10, - Accuracy: 90, - }, - { - Name: "生长", - Type: TypeGrass, - Effects: []Effect{ - &StatBoostEffect{Stat: "Attack", Modifier: 1.5, Turns: 2}, - &StatBoostEffect{Stat: "SpAttack", Modifier: 1.5, Turns: 2}, - }, - PP: 20, - MaxPP: 20, - Accuracy: 100, - }, - }, - } -} - -// 创建杰尼龟 -func createSquirtle() *Pokemon { - return &Pokemon{ - ID: "杰尼龟", - Type: TypeWater, - HP: 110, - MaxHP: 110, - Attack: 48, - Defense: 65, - SpAttack: 50, - SpDefense: 64, - Speed: 43, - Level: 5, - Skills: []*Skill{ - { - Name: "水枪", - Type: TypeWater, - Effects: []Effect{ - &DamageEffect{BasePower: 40}, - }, - PP: 25, - MaxPP: 25, - Accuracy: 100, - }, - { - Name: "水炮", - Type: TypeWater, - Effects: []Effect{ - &DamageEffect{BasePower: 110}, - }, - PP: 5, - MaxPP: 5, - Accuracy: 80, - }, - { - Name: "缩壳", - Type: TypeWater, - Effects: []Effect{ - &StatBoostEffect{Stat: "Defense", Modifier: 2.0, Turns: 4}, - }, - PP: 20, - MaxPP: 20, - Accuracy: 100, - }, - { - Name: "泡沫光线", - Type: TypeWater, - Effects: []Effect{ - &DamageEffect{BasePower: 65}, - &StatDebuffEffect{Stat: "Speed", Modifier: 1.5, Turns: 3}, - }, - PP: 20, - MaxPP: 20, - Accuracy: 100, - }, - }, - } -} - -// 创建小火龙 -func createCharmander() *Pokemon { - return &Pokemon{ - ID: "小火龙", - Type: TypeFire, - HP: 100, - MaxHP: 100, - Attack: 52, - Defense: 43, - SpAttack: 60, - SpDefense: 50, - Speed: 65, - Level: 5, - Skills: []*Skill{ - { - Name: "火苗", - Type: TypeFire, - Effects: []Effect{ - &DamageEffect{BasePower: 40}, - &StatusEffect{Name: "烧伤", Damage: 5, Turns: 3, CanAct: true}, - }, - PP: 25, - MaxPP: 25, - Accuracy: 100, - }, - { - Name: "火焰牙", - Type: TypeFire, - Effects: []Effect{ - &DamageEffect{BasePower: 65}, - }, - PP: 15, - MaxPP: 15, - Accuracy: 95, - }, - { - Name: "龙息", - Type: TypeFire, - Effects: []Effect{ - &DamageEffect{BasePower: 60}, - }, - PP: 20, - MaxPP: 20, - Accuracy: 100, - }, - { - Name: "瞪眼", - Type: TypeFire, - Effects: []Effect{ - &StatDebuffEffect{Stat: "Defense", Modifier: 1.5, Turns: 3}, - }, - PP: 30, - MaxPP: 30, - Accuracy: 100, - }, - }, - } -} - -// -------------------- 主函数 -------------------- // -func main() { - rand.Seed(time.Now().UnixNano()) - - // 初始化精灵 - pikachu := createPikachu() - bulbasaur := createBulbasaur() - //squirtle := createSquirtle() - charmander := createCharmander() - - // 选择战斗模式 - fmt.Println("请选择战斗模式:") - fmt.Println("1. PVE (玩家 vs AI)") - fmt.Println("2. PVP (玩家 vs 玩家)") - - reader := bufio.NewReader(os.Stdin) - var modeChoice int - - for { - fmt.Print("请输入选择 (1-2): ") - input, err := reader.ReadString('\n') - if err != nil { - fmt.Println("输入错误,请重试!") - continue - } - - input = strings.TrimSpace(input) - modeChoice, err = strconv.Atoi(input) - if err != nil || (modeChoice != 1 && modeChoice != 2) { - fmt.Println("无效选择,请重试!") - continue - } - - break - } - - var battle *BattleManager - var player1, player2 Player - - if modeChoice == 1 { // PVE - fmt.Println("\n===== PVE 战斗模式 =====") - battle = NewBattleManager(BattleMode.PVE) - - // 创建玩家和AI - player1 = NewHumanPlayer("玩家", []*Pokemon{pikachu}) - player2 = NewAIPlayer("野生精灵", []*Pokemon{bulbasaur}) - - fmt.Printf("\n你遇到了野生的 %s!\n", bulbasaur.ID) - } else { // PVP - fmt.Println("\n===== PVP 战斗模式 =====") - battle = NewBattleManager(BattleMode.PVP) - - // 创建两个玩家 - player1 = NewHumanPlayer("玩家1", []*Pokemon{pikachu}) - player2 = NewHumanPlayer("玩家2", []*Pokemon{charmander}) - - fmt.Println("\n玩家1 vs 玩家2") - } - - // 注册阶段钩子: - // 回合开始前:检查状态 - battle.On(PreTurn, func(ctx *BattleCtx) { - fmt.Printf("\n[回合开始前] %s (HP: %d/%d) 状态:\n", - ctx.User.ID, ctx.User.HP, ctx.User.MaxHP) - - // 显示状态 - if len(ctx.User.Statuses) == 0 { - fmt.Println(" 无异常状态") - } else { - for _, status := range ctx.User.Statuses { - fmt.Printf(" %s (剩余回合: %d)\n", status.Name, status.Turns) - } - } - - // 显示增益/减益 - if len(ctx.User.Buffs) > 0 { - fmt.Println(" 增益/减益:") - for _, buff := range ctx.User.Buffs { - fmt.Printf(" %s (剩余回合: %d)\n", buff.Name, buff.Turns) - } - } - }) - - // 回合结束后:处理持续状态 + 死亡判定 - battle.On(PostTurn, func(ctx *BattleCtx) { - // 1. 处理持续状态 - for i := len(ctx.User.Statuses) - 1; i >= 0; i-- { - status := ctx.User.Statuses[i] - - // 执行状态回合结束效果 - if status.OnTurnEnd != nil { - status.OnTurnEnd(ctx.User) - } - - // 减少持续回合 - status.Turns-- - if status.Turns <= 0 { - ctx.User.Statuses = append(ctx.User.Statuses[:i], ctx.User.Statuses[i+1:]...) - fmt.Printf(" → %s 的 %s 状态已解除!\n", ctx.User.ID, status.Name) - } - } - - // 2. 处理增益/减益回合 - for i := len(ctx.User.Buffs) - 1; i >= 0; i-- { - buff := ctx.User.Buffs[i] - buff.Turns-- - if buff.Turns <= 0 { - ctx.User.Buffs = append(ctx.User.Buffs[:i], ctx.User.Buffs[i+1:]...) - fmt.Printf(" → %s 的 %s 效果已结束!\n", ctx.User.ID, buff.Name) - } - } - - // 3. 死亡判定 - if ctx.User.HP <= 0 { - ctx.User.HP = 0 // 确保HP不小于0 - fmt.Printf(" → %s 被击败!\n", ctx.User.ID) - - // 检查是否所有精灵都已被击败 - if len(getAlivePokemons(ctx.Attacker.GetPokemons())) == 0 { - fmt.Printf("[%s] 所有精灵都已被击败,%s 获胜!\n", - ctx.Attacker.GetID(), ctx.Defender.GetID()) - battle.isOver = true - } else { - // 在实际游戏中,这里应该允许玩家更换精灵 - fmt.Println(" → 需要更换精灵(当前未实现此功能)") - } - } - }) - - // 启动战斗 - fmt.Println("\n===== 战斗开始 =====") - - // 决定先手顺序 - var first, second Player - if player1.GetActivePokemon().Speed >= player2.GetActivePokemon().Speed { - first = player1 - second = player2 - } else { - first = player2 - second = player1 - } - - fmt.Printf("\n%s 的 %s 速度更快,率先行动!\n", - first.GetID(), first.GetActivePokemon().ID) - - // 回合循环 - turn := 1 - for !battle.isOver { - fmt.Printf("\n===== 第 %d 回合 =====", turn) - fmt.Println("\n-------------------") - - // 先手行动 - battle.RunTurn(first, second) - if battle.isOver { - break - } - - // 后手行动 - battle.RunTurn(second, first) - - turn++ - } - - fmt.Println("\n===== 战斗结束 =====") -} diff --git a/logic/service/fight/fight.md b/logic/service/fight/fight.md new file mode 100644 index 000000000..d2316e881 --- /dev/null +++ b/logic/service/fight/fight.md @@ -0,0 +1,51 @@ +@startuml +skinparam classAttributeIconSize 0 + +class BattleUnit { + - Name: string + - Level: int + - Atk: int + - Def: int + - MaxHP: int + - HP: int + - Buffs: map +} + +class Skill { + - Name: string + - Type: SkillType + - Power: int + - Effects: []Effect +} + +class BattleContext { + - Rand: *rand.Rand + -- 全局战斗上下文信息 -- +} + +interface Effect { + + ID() string + + BeforeApplyBuff(attacker, defender *BattleUnit, skill *Skill, damageBuff *DamageBuff, ctx *BattleContext) + + AfterApplyBuff(skill *Skill, owner, target *BattleUnit, ownerDamage *int, targetDamage *int) error + + Apply(owner, target *BattleUnit, ctx *BattleContext) + + Next() bool +} + +class DamageEffect { + - id: string + - Power: int + - Duration: int +} + +class BuffManager { + + ProcessBuffs(unit *BattleUnit, target *BattleUnit, ctx *BattleContext) +} + +BattleUnit "1" *-- "*" Effect : Buffs +Skill "1" *-- "*" Effect : Effects +Effect <|.. DamageEffect +BattleUnit --> BattleContext : uses +Skill --> BattleContext : uses +BuffManager --> BattleUnit : manages Buffs + +@enduml diff --git a/logic/service/fight/fight_test.go b/logic/service/fight/fight_test.go new file mode 100644 index 000000000..b62b98140 --- /dev/null +++ b/logic/service/fight/fight_test.go @@ -0,0 +1,368 @@ +package fight + +import ( + "fmt" + "math/rand" + "testing" + "time" +) + +// ===== 战斗单位 ===== +type BattleUnit struct { + Name string + Level int + Atk int + Def int + MaxHP int + HP int + Buffs map[string]Effect + IsCritical bool + + // 阻止增益buff标记 + PreventBuff bool +} + +func NewBattleUnit(name string, level, atk, def, maxHP int) *BattleUnit { + return &BattleUnit{ + Name: name, + Level: level, + Atk: atk, + Def: def, + MaxHP: maxHP, + HP: maxHP, + Buffs: make(map[string]Effect), + } +} + +// ===== 事件接口及类型 ===== + +type BattleEvent interface { + Type() string +} + +type SkillUseEvent struct { + Source *BattleUnit + Target *BattleUnit + IsAttack bool // 是否攻击技能 +} + +func (e SkillUseEvent) Type() string { return "SkillUse" } + +// ===== 战斗上下文 ===== +type BattleContext struct { + Rand *rand.Rand + Events []BattleEvent +} + +// ===== Effect 接口 ===== +type Effect interface { + ID() string + Apply(attacker, defender *BattleUnit, ctx *BattleContext) + Next() bool +} + +// ===== Effect实现 ===== + +// 1. n回合内每回合恢复自身已损失体力的50% +type RecoverLostHPEffect struct { + id string + Duration int +} + +func (e *RecoverLostHPEffect) ID() string { return e.id } + +func (e *RecoverLostHPEffect) Apply(attacker, defender *BattleUnit, ctx *BattleContext) { + if e.Duration <= 0 { + return + } + lost := attacker.MaxHP - attacker.HP + heal := lost / 2 + if heal < 1 { + heal = 1 + } + attacker.HP += heal + if attacker.HP > attacker.MaxHP { + attacker.HP = attacker.MaxHP + } + fmt.Printf("[%s] 回合回复 %d 点已损失体力 (HP=%d)\n", attacker.Name, heal, attacker.HP) +} + +func (e *RecoverLostHPEffect) Next() bool { + e.Duration-- + return e.Duration > 0 +} + +// 2. 自身不处于能力强化状态时则YY能力变化(示例:攻击力+50) +type ConditionalBuffEffect struct { + id string + Duration int + BuffState string // 需要检测的强化状态ID + Applied bool + ChangeFunc func(unit *BattleUnit) + UndoFunc func(unit *BattleUnit) +} + +func (e *ConditionalBuffEffect) ID() string { return e.id } + +func (e *ConditionalBuffEffect) Apply(attacker, defender *BattleUnit, ctx *BattleContext) { + hasBuff := false + if _, ok := attacker.Buffs[e.BuffState]; ok { + hasBuff = true + } + if !hasBuff && !e.Applied { + if e.ChangeFunc != nil { + e.ChangeFunc(attacker) + } + e.Applied = true + fmt.Printf("[%s] 未处于强化状态,触发能力变化\n", attacker.Name) + } else if hasBuff && e.Applied { + if e.UndoFunc != nil { + e.UndoFunc(attacker) + } + e.Applied = false + fmt.Printf("[%s] 处于强化状态,撤销能力变化\n", attacker.Name) + } +} + +func (e *ConditionalBuffEffect) Next() bool { + e.Duration-- + if e.Duration <= 0 && e.Applied && e.UndoFunc != nil { + e.UndoFunc(nil) // 这里传nil简化逻辑,正常应传unit + } + return e.Duration > 0 +} + +// 3. n回合内若对手使用攻击技能降低对手最大体力的1/m +type ReduceMaxHPOnAttackEffect struct { + id string + Duration int + M int +} + +func (e *ReduceMaxHPOnAttackEffect) ID() string { return e.id } + +func (e *ReduceMaxHPOnAttackEffect) Apply(attacker, defender *BattleUnit, ctx *BattleContext) { + if e.Duration <= 0 { + return + } + for _, ev := range ctx.Events { + if ev.Type() == "SkillUse" { + su := ev.(SkillUseEvent) + if su.Source == defender && su.IsAttack { + reduce := defender.MaxHP / e.M + defender.MaxHP -= reduce + if defender.HP > defender.MaxHP { + defender.HP = defender.MaxHP + } + fmt.Printf("[%s] 使用攻击技能,被降低最大体力 %d (MaxHP=%d)\n", defender.Name, reduce, defender.MaxHP) + break + } + } + } +} + +func (e *ReduceMaxHPOnAttackEffect) Next() bool { + e.Duration-- + return e.Duration > 0 +} + +// 4. n回合对手无法使自身能力出现提升状态 +type PreventEnemyBuffEffect struct { + id string + Duration int +} + +func (e *PreventEnemyBuffEffect) ID() string { return e.id } + +func (e *PreventEnemyBuffEffect) Apply(attacker, defender *BattleUnit, ctx *BattleContext) { + // 标记对手PreventBuff + defender.PreventBuff = true + fmt.Printf("[%s] 使对手 [%s] 无法获得能力提升状态\n", attacker.Name, defender.Name) +} + +func (e *PreventEnemyBuffEffect) Next() bool { + e.Duration-- + return e.Duration > 0 +} + +// ===== 伤害计算函数 ===== + +func CalculateDamage(attacker, defender *BattleUnit, power int, isCritical bool, r *rand.Rand) int { + randomFactor := float64(r.Intn(39)+217) / 255.0 + levelZone := float64(attacker.Level*2) / float64(attacker.Level+defender.Level) + base := ((float64(attacker.Atk) * float64(power) / float64(defender.Def)) / 50.0) + 2 + + crit := 1.0 + if isCritical { + crit = 1.5 + } + + damage := int((base*levelZone*randomFactor)*crit + 0.5) + if damage < 1 { + damage = 1 + } + return damage +} + +// ===== 技能 ===== + +type Skill struct { + Name string + Effects []Effect + IsAttack bool // 标记是否攻击技能 +} + +func (s *Skill) Cast(attacker, defender *BattleUnit, ctx *BattleContext) { + fmt.Printf("\n>>> %s 使用技能 [%s] <<<\n", attacker.Name, s.Name) + + // 记录技能使用事件,供Effect消费 + ctx.Events = append(ctx.Events, SkillUseEvent{ + Source: attacker, + Target: defender, + IsAttack: s.IsAttack, + }) + + for _, effect := range s.Effects { + effect.Apply(attacker, defender, ctx) + if defender.HP <= 0 { + break + } + } +} + +// ===== 战斗状态机 ===== + +type BattleState int + +const ( + StateStart BattleState = iota + StatePlayerTurn + StateEnemyTurn + StateEnd +) + +type BattleStateMachine struct { + State BattleState +} + +func (fsm *BattleStateMachine) Next() { + switch fsm.State { + case StateStart: + fsm.State = StatePlayerTurn + case StatePlayerTurn: + fsm.State = StateEnemyTurn + case StateEnemyTurn: + fsm.State = StatePlayerTurn + } +} + +// ===== Buff处理 ===== + +func AddBuff(unit *BattleUnit, buff Effect) { + // 检查PreventBuff标志,阻止增益buff + if unit.PreventBuff { + fmt.Printf("[%s] 被阻止获得buff [%s]\n", unit.Name, buff.ID()) + return + } + unit.Buffs[buff.ID()] = buff + fmt.Printf("[%s] 获得buff [%s]\n", unit.Name, buff.ID()) +} + +func ProcessBuffs(unit *BattleUnit, target *BattleUnit, ctx *BattleContext) { + for id, buff := range unit.Buffs { + buff.Apply(unit, target, ctx) + if !buff.Next() { + fmt.Printf("[%s] 的buff [%s] 结束\n", unit.Name, id) + delete(unit.Buffs, id) + } + } + // 重置阻止buff标记,每回合重新计算 + unit.PreventBuff = false +} + +// ===== 测试战斗流程 ===== + +func TestBattleSystem(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + player := NewBattleUnit("雷伊", 100, 300, 200, 300) + enemy := NewBattleUnit("盖亚", 100, 280, 220, 300) + + ctx := &BattleContext{Rand: r} + + // 玩家技能:普通攻击 + 附加恢复buff(持续3回合) + playerSkill := &Skill{ + Name: "雷光闪", + Effects: []Effect{ + &RecoverLostHPEffect{id: "recover1", Duration: 3}, + }, + IsAttack: true, + } + + // 敌人技能:攻击并附加降低最大HP效果(持续2回合) + enemySkill := &Skill{ + Name: "岩石猛击", + Effects: []Effect{ + &ReduceMaxHPOnAttackEffect{id: "reduce_maxhp1", Duration: 2, M: 5}, + }, + IsAttack: true, + } + + // 玩家添加阻止敌人增益buffbuff(持续2回合) + preventBuff := &PreventEnemyBuffEffect{id: "prevent_buff1", Duration: 2} + AddBuff(player, preventBuff) + + // 玩家添加条件能力变化buff (攻击力+50),不处于强化状态时生效 + condBuff := &ConditionalBuffEffect{ + id: "cond_buff1", + Duration: 5, + BuffState: "attack_up", + ChangeFunc: func(unit *BattleUnit) { + if unit != nil { + unit.Atk += 50 + } + }, + UndoFunc: func(unit *BattleUnit) { + if unit != nil { + unit.Atk -= 50 + } + }, + } + AddBuff(player, condBuff) + + fsm := &BattleStateMachine{State: StateStart} + round := 1 + + for player.HP > 0 && enemy.HP > 0 { + fmt.Printf("\n===== 回合 %d =====\n", round) + ctx.Events = nil // 清空事件队列,准备本回合事件 + + switch fsm.State { + case StateStart: + fmt.Println("战斗开始!") + fsm.Next() + case StatePlayerTurn: + playerSkill.Cast(player, enemy, ctx) + ProcessBuffs(player, enemy, ctx) + ProcessBuffs(enemy, player, ctx) + fsm.Next() + case StateEnemyTurn: + enemySkill.Cast(enemy, player, ctx) + ProcessBuffs(player, enemy, ctx) + ProcessBuffs(enemy, player, ctx) + fsm.Next() + } + + if player.HP <= 0 || enemy.HP <= 0 { + break + } + round++ + } + + fmt.Println("\n=== 战斗结束 ===") + if player.HP > 0 { + fmt.Println("玩家胜利!") + } else { + fmt.Println("敌人胜利!") + } +} diff --git a/logic/service/fight/fsm_test.go b/logic/service/fight/fsm_test.go deleted file mode 100644 index df4d7ca7a..000000000 --- a/logic/service/fight/fsm_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package fight - -import ( - "context" - "fmt" - "testing" - - "github.com/looplab/fsm" -) - -func Test_main(t *testing.T) { - fsm := fsm.NewFSM( - "closed", - fsm.Events{ - {Name: "open", Src: []string{"closed"}, Dst: "open"}, - {Name: "close", Src: []string{"open"}, Dst: "closed"}, - }, - fsm.Callbacks{}, - ) - - fmt.Println(fsm.Current()) - - err := fsm.Event(context.Background(), "open") - if err != nil { - fmt.Println(err) - } - - fmt.Println(fsm.Current()) - - err = fsm.Event(context.Background(), "close") - if err != nil { - fmt.Println(err) - } - - fmt.Println(fsm.Current()) -}