1
This commit is contained in:
File diff suppressed because it is too large
Load Diff
51
logic/service/fight/fight.md
Normal file
51
logic/service/fight/fight.md
Normal 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
|
||||
368
logic/service/fight/fight_test.go
Normal file
368
logic/service/fight/fight_test.go
Normal 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("敌人胜利!")
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
Reference in New Issue
Block a user