This commit is contained in:
2025-08-11 21:16:25 +08:00
parent 917fc30f97
commit 4217defc01
4 changed files with 419 additions and 1111 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
@startuml
skinparam classAttributeIconSize 0
class BattleUnit {
- Name: string
- Level: int
- Atk: int
- Def: int
- MaxHP: int
- HP: int
- Buffs: map<string, Effect>
}
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

View File

@@ -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("敌人胜利!")
}
}

View File

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