Python 实战案例
"士气指数"在游戏 AI、团队管理模拟、军事推演、球员状态评估等场景中很常见,下面我用一个可运行的 Python 案例,展示如何把士气指数从"一个数字"变成"决策引擎"。

核心思路:士气不是直接决策,而是"调制器"
很多人的误区是:
if 士气 > 80: 进攻 elif 士气 < 30: 撤退
这样太粗糙,更好的架构是:
士气指数 → 分层处理 → 调节决策权重 → 加权评分 → 输出决策
具体分三层:
| 层级 | 作用 | 输出 |
|---|---|---|
| 状态层 | 士气映射为状态(高涨/正常/低落/崩溃) | 状态标签 |
| 权重层 | 状态调节各行动方案的权重 | 权重向量 |
| 决策层 | 加权评分 + 阈值/规则兜底 | 最终行动 |
完整案例:小队的战斗决策
from dataclasses import dataclass, field
from typing import List
# ---------- 1. 士气状态映射 ----------
def morale_to_state(morale: float) -> str:
"""士气 0-100 → 状态标签"""
if morale >= 80:
return "高涨"
elif morale >= 55:
return "正常"
elif morale >= 30:
return "低落"
else:
return "崩溃"
# ---------- 2. 状态 → 行动权重 ----------
# 权重系数调节各类行为的倾向
ACTION_WEIGHT_TABLE = {
# 进攻 防守 撤退 支援 鼓舞
"高涨": [1.4, 0.9, 0.2, 1.1, 1.3],
"正常": [1.0, 1.0, 1.0, 1.0, 1.0],
"低落": [0.6, 1.2, 1.0, 0.8, 1.4], # 低落更想防守/鼓舞
"崩溃": [0.2, 0.8, 1.8, 0.3, 1.5], # 崩溃倾向撤退/鼓舞
}
ACTIONS = ["进攻", "防守", "撤退", "支援", "鼓舞"]
# ---------- 3. 情境评分(不考虑士气时的基础决策) ----------
@dataclass
class Situation:
enemy_strength: float # 敌方强度 0-100
our_strength: float # 我方强度 0-100
ammo: float # 弹药 0-1
allies_nearby: bool # 是否有友军支援
objective_value: float # 目标价值 0-1
def base_scores(self) -> dict:
adv = (self.our_strength - self.enemy_strength) / 100 # -1 ~ 1
s = {}
s["进攻"] = 50 + 40 * adv + 30 * (self.ammo - 0.5) + 20 * self.objective_value
s["防守"] = 50 - 25 * adv + 15 * (self.ammo - 0.5)
s["撤退"] = 40 - 50 * adv + 20 * (1 - self.ammo)
s["支援"] = 40 + (30 if self.allies_nearby else -20)
s["鼓舞"] = 30 + (20 if not self.allies_nearby else 0)
return s
# ---------- 4. 决策引擎 ----------
@dataclass
class Commander:
name: str
morale: float = 60.0
history: List[str] = field(default_factory=list)
def decide(self, sit: Situation, verbose=True) -> str:
state = morale_to_state(self.morale)
weights = ACTION_WEIGHT_TABLE[state]
base = sit.base_scores()
# 加权求和
final = {a: base[a] * w for a, w in zip(ACTIONS, weights)}
# 规则兜底:崩溃状态下禁止主动进攻
if state == "崩溃":
final["进攻"] *= 0.3
# 连败惩罚:连续撤退过多,士气额外衰减(体现负反馈)
if self.history[-2:] == ["撤退", "撤退"]:
final["撤退"] *= 0.7
best = max(final, key=final.get)
if verbose:
print(f"[{self.name}] 士气={self.morale:.0f} 状态={state}")
for a in ACTIONS:
print(f" {a}: 基础{base[a]:6.1f} × {dict(zip(ACTIONS,weights))[a]} = {final[a]:5.1f}")
print(f" → 决策:**{best}**\n")
self.history.append(best)
return best
def update_morale(self, event: str):
"""事件驱动士气变化"""
delta = {
"胜利": +15, "失败": -20, "队友阵亡": -25,
"获得补给": +10, "被鼓舞": +12,
}.get(event, 0)
self.morale = max(0, min(100, self.morale + delta))
# ---------- 5. 演练 ----------
if __name__ == "__main__":
sit = Situation(enemy_strength=70, our_strength=55,
ammo=0.4, allies_nearby=False, objective_value=0.8)
cmdr = Commander("Alpha小队", morale=85)
cmdr.decide(sit)
cmdr.update_morale("失败")
cmdr.update_morale("队友阵亡") # 士气 85 → 40
cmdr.decide(sit)
cmdr.update_morale("获得补给")
cmdr.decide(sit)
输出大致如下:
[Alpha小队] 士气=85 状态=高涨
进攻: 基础 44.0 × 1.4 = 61.6 ← 士气高涨,主动进攻
...
→ 决策:**进攻**
[Alpha小队] 士气=40 状态=低落
进攻: 基础 44.0 × 0.6 = 26.4
撤退: 基础 36.0 × 1.0 = 36.0
→ 决策:**撤退** ← 士气低落+连败,转向保守
[Alpha小队] 士气=50 状态=低落
→ 决策:**防守**
让士气"活起来"的 4 个关键设计
士气不是单值,而是一个"分布"
真实场景里士气波动大,可以用概率分布代替确定值:
import numpy as np
def sample_morale(mu, sigma=8):
return float(np.clip(np.random.normal(mu, sigma), 0, 100))
# 蒙特卡洛:跑 1000 次看决策分布
decisions = {}
for _ in range(1000):
cmdr.morale = sample_morale(55)
d = cmdr.decide(Situation(...), verbose=False)
decisions[d] = decisions.get(d, 0) + 1
# → 得到"60%防守 / 25%进攻 / 15%撤退"这样更真实的分布
士气要有"惯性"和"记忆"
用指数平滑避免士气剧烈抖动:
self.morale = 0.7 * self.morale + 0.3 * new_signal
或者引入士气衰退——长期不鼓舞自动下滑:
def tick(self, hours=1):
self.morale = max(0, self.morale - 0.5 * hours)
士气影响的不只是选择,还有"执行质量"
士气低落时,即使选了进攻也可能执行打折:
effectiveness = 0.5 + 0.5 * (self.morale / 100) # 0.5~1.0 的执行系数 damage_dealt = base_damage * effectiveness
决策时要区分"高士气≠莽"
高士气应该减少迟疑而不是无脑进攻,可以这样做:
if state == "高涨":
# 高涨时缩短决策延迟、提高执行速度
reaction_time *= 0.7
# 但保留风险评估
else:
reaction_time *= 1.5
更通用的接口化设计(推荐)
把上面耦合的逻辑抽成可替换的策略接口,方便迁移到其他领域(团队管理、机器人、NPC AI):
from abc import ABC, abstractmethod
class MoralePolicy(ABC):
@abstractmethod
def weight(self, state: str) -> dict: ...
class DecisionEnv(ABC):
@abstractmethod
def base_scores(self) -> dict: ...
class MoraleDecisionEngine:
def __init__(self, policy: MoralePolicy, env: DecisionEnv):
self.policy = policy
self.env = env
def decide(self, morale: float) -> str:
state = morale_to_state(morale)
w = self.policy.weight(state)
scores = {a: self.env.base_scores()[a] * w[a] for a in w}
return max(scores, key=scores.get)
这样你可以:
- 游戏里用
CombatPolicy(进攻/防守倾向) - 团队管理模拟里用
TeamPolicy(激励/加班/休假) - 机器人在危险环境下用
SafetyPolicy(探索/保守)
决策时机的几个坑
| 坑 | 后果 | 解法 |
|---|---|---|
| 士气值直接当权重,不做归一 | 数值尺度失真,被其他因素淹没 | 映射到 0.2~1.8 的调节系数 |
| 只看当前士气,忽略趋势 | 高涨刚开始回落就误判进攻 | 加入士气变化率 Δmorale/dt |
| 士气崩溃仍按加权决策 | 可能出现"自杀式选择" | 加规则兜底/硬约束 |
| 忽视士气反馈回路 | 撤退→士气更低→更想撤退(死循环) | 撤退后强制降低撤退权重 + 加冷静期 |
总结一句话
士气指数不是"if 语句",而是"乘法系数 + 状态机 + 反馈环"。
- 用状态映射把连续值变成离散档位 → 可解释
- 用权重表让士气调制各方案得分 → 可调参
- 用规则兜底防止极端情况 → 可安全
- 用单调更新 + 惯性让士气有记忆 → 可仿真
需要我针对游戏 NPC、团队调度、还是部队推演哪个具体场景展开更细的参数调优吗?