类型

传统指令式回合制
代表:《梦幻西游》《宝可梦》《最终幻想》早期作品、《仙剑》战斗
核心需求:
-
双方轮流下达指令(攻击/技能/道具/逃跑)
-
速度属性决定同一回合内谁先出手
-
一个完整回合 = 所有单位各行动一次
-
战斗结果是确定性的(同样操作得到同样结果),便于做战斗录像和平衡
python
import random
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class CommandType(Enum):
ATTACK = 1
SKILL = 2
DEFEND = 3
ITEM = 4
FLEE = 5
@dataclass
class Unit:
name: str
hp: int
max_hp: int
mp: int
max_mp: int
atk: int
defense: int
speed: int # 决定回合内出手顺序
defending: bool = False
alive: bool = True
def take_damage(self, dmg: int) -> int:
if self.defending:
dmg = max(1, dmg // 2)
else:
dmg = max(1, dmg)
self.hp = max(0, self.hp - dmg)
if self.hp == 0:
self.alive = False
return dmg
class ClassicTurnBattle:
def __init__(self, allies: list[Unit], enemies: list[Unit]):
self.allies = allies
self.enemies = enemies
self.round = 0
self.log: list[str] = []
def _alive_units(self) -> list[Unit]:
return [u for u in self.allies + self.enemies if u.alive]
def _enemies_of(self, unit: Unit) -> list[Unit]:
pool = self.enemies if unit in self.allies else self.allies
return [u for u in pool if u.alive]
def _allies_of(self, unit: Unit) -> list[Unit]:
pool = self.allies if unit in self.allies else self.enemies
return [u for u in pool if u.alive]
def _deal_damage(self, attacker: Unit, target: Unit) -> int:
raw = attacker.atk * 2 - target.defense
crit = random.random() < 0.05 # 5% 暴击
dmg = max(1, int(raw * (1.5 if crit else 1)))
dealt = target.take_damage(dmg)
self.log.append(
f"{attacker.name} 攻击 {target.name} 造成 {dealt} 伤害"
f"{'(暴击!)' if crit else ''}"
)
return dealt
def execute_round(self, commands: dict[Unit, tuple[CommandType, Optional[Unit]]]):
"""commands: {unit: (command_type, target)}"""
self.round += 1
self.log.append(f"===== 第 {self.round} 回合 =====")
# 1. 按速度降序决定出手顺序
turn_order = sorted(
[u for u in commands.keys() if u.alive],
key=lambda u: -u.speed
)
for actor in turn_order:
if not actor.alive:
continue
cmd, target = commands[actor]
# 重置防御姿态
actor.defending = False
if cmd == CommandType.ATTACK:
if target is None or not target.alive:
target = random.choice(self._enemies_of(actor))
self._deal_damage(actor, target)
elif cmd == CommandType.DEFEND:
actor.defending = True
self.log.append(f"{actor.name} 进入防御姿态")
elif cmd == CommandType.FLEE:
if random.random() < 0.5:
self.log.append(f"{actor.name} 逃跑成功!战斗结束")
return True # 逃跑成功
else:
self.log.append(f"{actor.name} 逃跑失败")
# 死亡检查
if all(not u.alive for u in self._enemies_of(actor)):
self.log.append("敌方全灭,战斗胜利!")
return True
if all(not u.alive for u in self._allies_of(actor)):
self.log.append("我方全灭,战斗失败...")
return True
# 回合结束:清除本回合的防御标记(为下回合准备)
for u in self._alive_units():
u.defending = False
return False
行动条驱动半即时
代表:《阴阳师》《崩坏:星穹铁道》《最终幻想》ATB 版本、你前面问的三国幻想志类
核心需求:
-
没有"回合"概念,每个单位有独立的行动条
-
行动条按单位速度累积,满了就行动一次,然后清零
-
行动顺序不是固定轮换,而是动态竞争
-
技能可以"推条/拉条"------改变行动条进度,这是策略深度的核心来源
-
表现层通常是"走条"动画
python
import random
from dataclasses import dataclass
from typing import Optional
@dataclass
class ATBUnit:
name: str
hp: int
max_hp: int
atk: int
speed: int # 行动条累积速度
gauge: float = 0.0 # 当前行动条 [0, 100]
alive: bool = True
def fill_gauge(self, dt: float):
if self.alive:
self.gauge = min(100.0, self.gauge + self.speed * dt)
def reset_gauge(self):
self.gauge = 0.0
class ATBBattle:
"""行动条驱动的半即时战斗"""
def __init__(self, allies: list[ATBUnit], enemies: list[ATBUnit]):
self.units = allies + enemies
self.allies = allies
self.enemies = enemies
self.log: list[str] = []
self.time = 0.0
self.dt = 0.1 # 时间步长
def _enemies_of(self, unit: ATBUnit) -> list[ATBUnit]:
pool = self.enemies if unit in self.allies else self.allies
return [u for u in pool if u.alive]
def _pick_target(self, attacker: ATBUnit) -> Optional[ATBUnit]:
"""简单 AI:选敌方血量最低的"""
enemies = self._enemies_of(attacker)
if not enemies:
return None
return min(enemies, key=lambda e: e.hp / e.max_hp)
def _cast_skill_with_push(self, caster: ATBUnit, target: ATBUnit):
"""
释放技能,并演示推条/拉条机制
- 自身行动条清零(已行动)
- 目标被'推条':行动条减少,延迟其下次行动
"""
# 伤害结算
dmg = int(caster.atk * 1.8)
target.hp = max(0, target.hp - dmg)
if target.hp == 0:
target.alive = False
self.log.append(
f"[{self.time:.1f}s] {caster.name} 释放技能 → {target.name} 造成 {dmg} 伤害"
)
# 推条:让目标行动条后退 30%
if target.alive:
target.gauge = max(0, target.gauge - 30)
self.log.append(f" └ 推条:{target.name} 行动条 -30")
# 自身行动条清零
caster.reset_gauge()
def run(self, max_time: float = 60.0):
"""主循环:时间驱动"""
self.log.append("===== 战斗开始(行动条驱动)=====")
while self.time < max_time:
self.time += self.dt
# 1. 所有存活单位累积行动条
for u in self.units:
u.fill_gauge(self.dt)
# 2. 找出行动条满的单位
ready = [u for u in self.units if u.alive and u.gauge >= 100]
if not ready:
continue
# 3. 如果有多个就绪,速度最高的先动
actor = max(ready, key=lambda u: u.speed)
# 4. 选目标并行动
target = self._pick_target(actor)
if target is None:
self.log.append("战斗结束")
break
# 50% 普通攻击,50% 技能(演示推条)
if random.random() < 0.5:
dmg = max(1, actor.atk - target.atk // 2)
target.hp = max(0, target.hp - dmg)
if target.hp == 0:
target.alive = False
actor.reset_gauge()
self.log.append(
f"[{self.time:.1f}s] {actor.name} 普通攻击 → {target.name} 造成 {dmg} 伤害"
)
else:
self._cast_skill_with_push(actor, target)
# 5. 胜负判定
if not any(u.alive for u in self.enemies):
self.log.append(f"[{self.time:.1f}s] 战斗胜利!")
break
if not any(u.alive for u in self.allies):
self.log.append(f"[{self.time:.1f}s] 战斗失败...")
break
return self.log
九宫格 / 自走棋式自动战斗
代表:《三国幻想志2》《自走棋》《领主冲突》《止戈之战》《真理之拳》
核心需求:
-
战前布阵(3×3 或类似网格),战时不可操作
-
单位按行动条/速度决定出手顺序
-
有空间关系:单位在网格上有坐标,攻击有范围
-
目标选择按规则:最前排优先 → 同列优先 → 距离最近
-
不在攻击范围内 → 向目标移动一格
-
表现层是"武将走过去打一下"的动画
python
import math
import random
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class Faction(Enum):
ALLY = 1
ENEMY = 2
@dataclass
class GridUnit:
name: str
faction: Faction
hp: int
max_hp: int
atk: int
speed: int
pos: tuple[int, int] # (row, col) 九宫格坐标
attack_range: int = 1
gauge: float = 0.0
alive: bool = True
def fill_gauge(self, dt: float):
if self.alive:
self.gauge = min(100.0, self.gauge + self.speed * dt)
class GridAutoBattle:
"""
九宫格自动战斗:战前布阵,战时全自动
棋盘:3x3,ally 在左三列 (col 0,1,2),enemy 在右三列 (col 6,7,8)
或者更紧凑:ally 占 col 0,1,enemy 占 col 1,2(共用中线)
"""
def __init__(self, allies: list[GridUnit], enemies: list[GridUnit]):
# 简化:ally 在 col 0,enemy 在 col 2
self.allies = allies
self.enemies = enemies
self.units = allies + enemies
self.log: list[str] = []
self.time = 0.0
self.dt = 0.1
def _enemies_of(self, unit: GridUnit) -> list[GridUnit]:
pool = self.enemies if unit.faction == Faction.ALLY else self.allies
return [u for u in pool if u.alive]
def _select_target(self, attacker: GridUnit) -> Optional[GridUnit]:
"""
目标选择规则(九宫格优先级):
1. 优先攻击最前排(离自己最近的列)
2. 同排优先同列
3. 都没有则曼哈顿距离最近
"""
enemies = self._enemies_of(attacker)
if not enemies:
return None
ar, ac = attacker.pos
def priority_key(e: GridUnit):
er, ec = e.pos
# 1. 列距离(越小越靠前)
col_dist = abs(ec - ac)
# 2. 是否同列(同列优先)
same_col = 0 if ec == ac else 1
# 3. 曼哈顿距离
manhattan = abs(er - ar) + col_dist
return (col_dist, same_col, manhattan)
return min(enemies, key=priority_key)
def _manhattan(self, a: tuple, b: tuple) -> int:
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def _can_attack(self, attacker: GridUnit, target: GridUnit) -> bool:
return self._manhattan(attacker.pos, target.pos) <= attacker.attack_range
def _step_toward(self, attacker: GridUnit, target: GridUnit):
"""向目标移动一格(贪心:先对齐行,再对齐列)"""
ar, ac = attacker.pos
tr, tc = target.pos
if ar < tr:
ar += 1
elif ar > tr:
ar -= 1
elif ac < tc:
ac += 1
elif ac > tc:
ac -= 1
old_pos = attacker.pos
attacker.pos = (ar, ac)
self.log.append(
f"[{self.time:.1f}s] {attacker.name} 移动:{old_pos} → {attacker.pos}"
)
def run(self, max_time: float = 120.0):
self.log.append("===== 九宫格自动战斗开始 =====")
while self.time < max_time:
self.time += self.dt
# 1. 累积行动条
for u in self.units:
u.fill_gauge(self.dt)
# 2. 找到行动条满的单位
ready = [u for u in self.units if u.alive and u.gauge >= 100]
if not ready:
continue
# 3. 行动顺序:速度 → 战力(这里用 atk 近似)→ 站位
actor = max(ready, key=lambda u: (u.speed, u.atk))
# 4. 选目标
target = self._select_target(actor)
if target is None:
self.log.append("战斗结束")
break
# 5. 判断攻击 or 移动
if self._can_attack(actor, target):
# 在范围内,发动攻击
dmg = max(1, actor.atk - target.atk // 3)
target.hp = max(0, target.hp - dmg)
if target.hp == 0:
target.alive = False
actor.gauge = 0.0
self.log.append(
f"[{self.time:.1f}s] {actor.name} 攻击 {target.name} "
f"造成 {dmg} 伤害 (HP: {target.hp}/{target.max_hp})"
)
else:
# 不在范围内,向目标移动一格
actor.gauge = 0.0 # 移动也消耗本次行动机会
self._step_toward(actor, target)
# 6. 胜负判定
if not any(u.alive for u in self.enemies):
self.log.append(f"[{self.time:.1f}s] 战斗胜利!")
break
if not any(u.alive for u in self.allies):
self.log.append(f"[{self.time:.1f}s] 战斗失败...")
break
return self.log
挂机 / 放置类自动战斗
代表:各种 Idle Game、放置 RPG、《剑与远征》AFK 系统
核心需求:
-
完全没有玩家战时操作
-
战前配置队伍和站位,然后离线也能战斗
-
战斗逻辑必须纯函数化、可序列化、可加速模拟
-
通常批量模拟成千上万场战斗(推图、挂机收益计算)
python
import random
from dataclasses import dataclass, replace
from typing import Optional
@dataclass
class IdleUnit:
name: str
faction: Faction
hp: int
max_hp: int
atk: int
speed: int
pos: tuple[int, int]
attack_range: int = 1
gauge: float = 0.0
alive: bool = True
def clone(self) -> 'IdleUnit':
"""深拷贝,用于批量模拟"""
return replace(self)
class IdleAutoBattle:
"""
挂机战斗:无渲染、可批量、可加速
关键设计:战斗过程是纯函数,给定初始状态和随机种子,结果完全确定
"""
def __init__(self, allies: list[IdleUnit], enemies: list[IdleUnit], seed: int = 42):
self.initial_allies = [u.clone() for u in allies]
self.initial_enemies = [u.clone() for u in enemies]
self.seed = seed
def simulate_one_battle(self) -> dict:
"""模拟一场战斗,返回结果统计"""
random.seed(self.seed) # 固定种子保证可复现
# 重置战斗状态
allies = [u.clone() for u in self.initial_allies]
enemies = [u.clone() for u in self.initial_enemies]
all_units = allies + enemies
time = 0.0
dt = 0.1
max_time = 300.0
while time < max_time:
time += dt
# 累积行动条
for u in all_units:
if u.alive:
u.gauge = min(100.0, u.gauge + u.speed * dt)
# 找行动单位
ready = [u for u in all_units if u.alive and u.gauge >= 100]
if not ready:
continue
actor = max(ready, key=lambda u: (u.speed, u.atk))
# 选目标(敌方存活单位)
enemy_pool = enemies if actor.faction == Faction.ALLY else allies
alive_enemies = [e for e in enemy_pool if e.alive]
if not alive_enemies:
break
target = min(alive_enemies, key=lambda e: e.hp / e.max_hp)
# 攻击 or 移动(逻辑同上,这里简化只做攻击)
if actor.attack_range >= abs(target.pos[1] - actor.pos[1]):
dmg = max(1, actor.atk - target.atk // 3 + random.randint(-2, 2))
target.hp = max(0, target.hp - dmg)
if target.hp == 0:
target.alive = False
actor.gauge = 0.0
else:
# 移动:简化版,向目标列靠近
ar, ac = actor.pos
_, tc = target.pos
if ac < tc:
ac += 1
elif ac > tc:
ac -= 1
actor.pos = (ar, ac)
actor.gauge = 0.0
# 胜负
if not any(e.alive for e in enemies):
return {
"result": "win",
"time": time,
"remaining_hp_percent": sum(u.hp for u in allies if u.alive) /
sum(u.max_hp for u in self.initial_allies)
}
if not any(a.alive for a in allies):
return {"result": "lose", "time": time, "remaining_hp_percent": 0.0}
return {"result": "timeout", "time": time, "remaining_hp_percent": 0.0}
def batch_simulate(self, n: int = 1000) -> dict:
"""批量模拟 n 场,统计胜率"""
wins = 0
total_hp_percent = 0.0
for i in range(n):
self.seed = 42 + i # 每场不同种子
result = self.simulate_one_battle()
if result["result"] == "win":
wins += 1
total_hp_percent += result["remaining_hp_percent"]
return {
"total": n,
"wins": wins,
"win_rate": wins / n,
"avg_remaining_hp_on_win": total_hp_percent / max(1, wins)
}
# 使用示例
if __name__ == "__main__":
# 配置两支队伍
allies = [
IdleUnit("赵云", Faction.ALLY, 1000, 1000, 150, 120, (0, 0)),
IdleUnit("关羽", Faction.ALLY, 1200, 1200, 130, 100, (1, 0)),
IdleUnit("张飞", Faction.ALLY, 1100, 1100, 140, 90, (2, 0)),
]
enemies = [
IdleUnit("吕布", Faction.ENEMY, 1500, 1500, 160, 130, (0, 2)),
IdleUnit("董卓", Faction.ENEMY, 1800, 1800, 120, 80, (1, 2)),
]
battle = IdleAutoBattle(allies, enemies)
stats = battle.batch_simulate(1000)
print(f"胜率: {stats['win_rate']:.1%}")
print(f"胜利时平均剩余血量: {stats['avg_remaining_hp_on_win']:.1%}")