尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python实现宝可梦逆属性对战模拟器:属性克制与伤害计算

Python实现宝可梦逆属性对战模拟器:属性克制与伤害计算 在 B 站刷到一个宝可梦动画剪辑标题叫“喵神逆属性斩杀大电海燕呆火鳄终于进化”。评论区和弹幕都在讨论“逆属性到底怎么算”“呆火鳄进化之后能打过电海燕吗”。我第一反应不是去吵战力而是想这种“逆属性斩杀”的对战逻辑能不能用代码算出来答案当然是可以。宝可梦对战本质上是一套属性克制表的计算问题招式属性、宝可梦属性、克制倍率、本系加成组合在一起就决定了伤害高低。如果把它抽象成数据结构和算法我们完全可以用 Python 做一个“宝可梦逆属性对战模拟器”把喵神、大电海燕、呆火鳄进化前后的战斗力都量化出来。这个项目并不复杂非常适合用来练手 Python 基础、字典建模、面向对象思想和简单 CLI 工具设计。本文会从属性克制表讲起逐步实现一个可运行的对战模拟程序并用“喵神 vs 大电海燕”“呆火鳄进化前后强度对比”两个案例来验证。最后还会扩展一个自动生成剪辑字幕的小功能正好对上动漫剪辑的场景。学完你不仅能用代码复现逆属性对战还能把这个思路迁移到其他规则型业务系统里。1. 背景与核心概念1.1 什么是“逆属性对战”“逆属性”并不是一个严格的官方术语而是玩家圈子里的常用说法。简单来说就是用属性上处于劣势的宝可梦或招式去对抗对方最终还能打出伤害甚至完成击杀。在宝可梦的对战规则里每个招式都有自己的属性每只宝可梦也有一到两个属性。当一个招式打过去时系统会根据招式属性和目标宝可梦属性计算克制倍率。一般来说倍率大于 1说明克制对方伤害会提升。倍率等于 1说明属性上不存在克制或抵抗。倍率小于 1说明被对方抵抗伤害降低。倍率为 0说明完全无效。“逆属性斩杀”最常见的情况就是招式属性被目标抵抗倍率小于 1但因为攻击方等级、攻击力、特性、道具、技能效果等机制足够强仍然打出了可观甚至反杀的伤害。这种局面在动画剪辑里非常有戏剧性所以经常被做成高燃片段。1.2 属性克制表是核心规则宝可梦正统游戏里目前有 18 种属性每一种属性攻击其他属性时都有固定的克制倍率。例如火系招式打草系、虫系、冰系、钢系时是 2 倍伤害。火系招式打水系、岩石系、龙系时只有 0.5 倍伤害。普通系招式打幽灵系时是 0 倍伤害也就是打不中。当一只宝可梦有双属性时攻击方对它的克制倍率需要分别计算然后把两次结果相乘。例如大电海燕是“电 飞行”双属性地面系招式打它时地面系对电系是 2 倍对飞行系是 0 倍最终合成倍率是 2 × 0 0所以地面系招式打电海燕完全无效。这种“属性乘法叠加”的规则非常适合用代码表达也适合做自动化计算。我们只要维护一张完整的 18 × 18 克制表再写一个函数负责“招式属性 × 防御方所有属性”就能得到正确的克制倍率。1.3 为什么用 Python 来模拟Python 语言语法简单标准库自带的字典结构非常适合表达“克制关系”这种映射表不需要引入额外的数据库或框架。一个单文件脚本就能完成属性克制表定义。宝可梦数据建模。伤害计算。对战日志输出。字幕文件生成。对于刚接触 Python 的开发者来说这是一个很好的综合练习能同时覆盖字典、列表、函数、类、文件读写等知识点。对于有经验的开发者也可以快速扩展成 Web API、数据分析工具或剪辑辅助插件。2. 环境准备与项目结构2.1 运行环境本文示例代码基于 Python 3建议使用 Python 3.8 或更高版本。完整代码只依赖标准库不需要安装第三方包。如果你的电脑还没有 Python 环境可以用以下命令检查python --version在 Windows 上如果命令不是python而是python3请根据系统情况调整。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。只要 Python 版本在 3.8 以上代码基本可以正常运行。2.2 项目目录规划为了后续扩展方便我建议把代码拆成两个文件一个用于存放数据一个用于模拟对战。pokemon-battle-simulator/ ├── pokemon_data.py # 属性克制表、宝可梦数据、克制倍率计算函数 └── battle_simulator.py # 对战流程、结果输出、字幕导出如果你的需求比较简单也可以把所有代码合并到一个main.py文件中。本文为了讲解清晰会先在pokemon_data.py里完成数据层设计再在battle_simulator.py里实现业务逻辑。最后会给出完整的运行示例。2.3 需要理解的核心设计思路整个项目围绕三件事展开数据层属性克制表、宝可梦种族值、招式威力。计算层根据属性克制乘法和本系加成计算最终倍率与伤害。展示层输出对战结果甚至生成剪辑字幕文件。把数据、计算、展示分离之后以后想改成 Web 版、GUI 版只需要替换展示层数据层和计算层不需要变动。3. 属性克制关系建模3.1 18 种属性一览宝可梦的 18 种常见属性分别是普通、火、水、电、草、冰、格斗、毒、地面、飞行、超能力、虫、岩石、幽灵、龙、恶、钢、妖精。在代码中我会用一个字典来存储“某个攻击属性对所有防御属性的效果倍率”。只存储非 1 倍的情况这样可以减少重复数据也更容易阅读。3.2 定义属性克制表下面是pokemon_data.py中的属性克制表定义。这个表参考了宝可梦正统游戏的经典克制关系你可以根据自己的需求增删属性或调整倍率。# -*- coding: utf-8 -*- 数据层属性克制表、宝可梦数据、克制倍率计算函数 TYPE_EFFECTIVENESS { 普通: {岩石: 0.5, 钢: 0.5, 幽灵: 0.0}, 火: {草: 2.0, 虫: 2.0, 冰: 2.0, 钢: 2.0, 火: 0.5, 水: 0.5, 龙: 0.5, 岩石: 0.5}, 水: {火: 2.0, 地面: 2.0, 岩石: 2.0, 水: 0.5, 草: 0.5, 龙: 0.5}, 电: {水: 2.0, 飞行: 2.0, 电: 0.5, 草: 0.5, 龙: 0.5, 地面: 0.0}, 草: {水: 2.0, 地面: 2.0, 岩石: 2.0, 火: 0.5, 草: 0.5, 毒: 0.5, 飞行: 0.5, 虫: 0.5, 钢: 0.5, 龙: 0.5}, 冰: {草: 2.0, 地面: 2.0, 飞行: 2.0, 龙: 2.0, 火: 0.5, 水: 0.5, 冰: 0.5, 钢: 0.5}, 格斗: {普通: 2.0, 冰: 2.0, 岩石: 2.0, 钢: 2.0, 恶: 2.0, 毒: 0.5, 飞行: 0.5, 超能力: 0.5, 虫: 0.5, 妖精: 0.5, 幽灵: 0.0}, 毒: {草: 2.0, 妖精: 2.0, 毒: 0.5, 地面: 0.5, 岩石: 0.5, 幽灵: 0.5, 钢: 0.0}, 地面: {火: 2.0, 电: 2.0, 毒: 2.0, 岩石: 2.0, 钢: 2.0, 草: 0.5, 虫: 0.5, 飞行: 0.0}, 飞行: {草: 2.0, 虫: 2.0, 格斗: 2.0, 电: 0.5, 岩石: 0.5, 钢: 0.5}, 超能力: {格斗: 2.0, 毒: 2.0, 超能力: 0.5, 钢: 0.5, 恶: 0.0}, 虫: {草: 2.0, 超能力: 2.0, 恶: 2.0, 火: 0.5, 格斗: 0.5, 毒: 0.5, 飞行: 0.5, 幽灵: 0.5, 钢: 0.5, 妖精: 0.5}, 岩石: {火: 2.0, 冰: 2.0, 虫: 2.0, 飞行: 2.0, 格斗: 0.5, 地面: 0.5, 钢: 0.5}, 幽灵: {超能力: 2.0, 幽灵: 2.0, 恶: 0.5, 普通: 0.0}, 龙: {龙: 2.0, 钢: 0.5, 妖精: 0.0}, 恶: {超能力: 2.0, 幽灵: 2.0, 格斗: 0.5, 恶: 0.5, 妖精: 0.5}, 钢: {冰: 2.0, 岩石: 2.0, 妖精: 2.0, 火: 0.5, 水: 0.5, 电: 0.5, 钢: 0.5}, 妖精: {格斗: 2.0, 龙: 2.0, 恶: 2.0, 火: 0.5, 毒: 0.5, 钢: 0.5}, } ALL_TYPES list(TYPE_EFFECTIVENESS.keys())这里的关键点在于字典中的默认碰撞倍率是 1.0只有存在克制、抵抗或免疫关系时才显式写出。例如普通: {岩石: 0.5}表示普通系招式打岩石系只有 0.5 倍伤害。3.3 计算单属性克制倍率有了克制表我们就能计算任意攻击属性对目标宝可梦的效果倍率。先看基础版本def effect_value(attack_type, defense_types): 计算攻击属性对防御方可能包含多个属性的综合克制倍率。 :param attack_type: 招式属性例如 火 :param defense_types: 防御方属性列表例如 [草, 龙] :return: 最终克制倍率 value 1.0 for dt in defense_types: value * TYPE_EFFECTIVENESS.get(attack_type, {}).get(dt, 1.0) return value这个函数的核心逻辑是把防御方的每个属性分开计算然后相乘。例如“火系招式打草 龙属性的宝可梦”火系对草系是 2.0。火系对龙系是 0.5。综合倍率 2.0 × 0.5 1.0。如果只保留一个防御属性函数也完全适用因为defense_types只包含一个元素。3.4 判断逆属性所谓“逆属性”从数值层面看就是综合克制倍率小于 1。我们可以在计算后加一个判定def describe_multiplier(multiplier): 把克制倍率转换成人类可读的描述。 if multiplier 0: return 完全无效 if multiplier 1: return 逆属性效果不佳 if multiplier 1: return 普通伤害 return 克制伤害需要注意的是逆属性并不等于不能打。动画剪辑里的“逆属性斩杀”往往依赖更高的攻击数值、招式威力、本系加成、特性触发等。因此我们在模拟器里除了算克制倍率也要把攻击方等级、攻击力、防御方防御力、招式威力一起纳入伤害计算。4. 核心功能实现对战模拟器4.1 宝可梦数据类为了描述一只宝可梦我们至少需要知道它的名字、属性、基础攻击、基础防御和等级。这里用类来抽象class Pokemon: 宝可梦数据模型。 def __init__(self, name, types, base_attack100, base_defense100, level50): self.name name self.types types self.base_attack base_attack self.base_defense base_defense self.level level def attack_stat(self): 根据基础攻击和等级估算攻击力。 return int(self.base_attack * 0.4 self.level * 0.6) def defense_stat(self): 根据基础防御和等级估算防御力。 return int(self.base_defense * 0.4 self.level * 0.6) def info(self): 返回易读的宝可梦描述。 return f{self.name}{/.join(self.types)}这里的attack_stat和defense_stat是简化的属性值估算函数。实际游戏里有更复杂的种族值、个体值、努力值、性格修正等机制本文只需要一个可对比的数值模型因此没有过度展开。4.2 伤害计算公式宝可梦游戏的实际伤害公式比较复杂包含随机数、防御修正、天气、场地等多重因素。为了聚焦属性克制这个核心主题我采用一个简化公式def calculate_damage(attacker, defender, move_power, move_type, stabTrue): 计算一次招式攻击的伤害。 :param attacker: 攻击方 Pokemon 实例 :param defender: 防御方 Pokemon 实例 :param move_power: 招式威力 :param move_type: 招式属性 :param stab: 是否享受本系加成Same Type Attack Bonus if move_type not in ALL_TYPES: raise ValueError(f未知属性{move_type}) multiplier effect_value(move_type, defender.types) stab_bonus 1.5 if stab and move_type in attacker.types else 1.0 damage (move_power * attacker.attack_stat() / defender.defense_stat()) * multiplier * stab_bonus return { attacker: attacker.info(), defender: defender.info(), move_type: move_type, move_power: move_power, multiplier: multiplier, stab: stab_bonus, damage: round(damage, 2), }这个函数里有一个细节stab参数用于控制本系加成。当宝可梦使用的招式属性与自身属性一致时伤害会额外乘以 1.5这就是“本系加成”。例如火系的呆火鳄使用火系招式就会吃到本系加成。4.3 模拟一次对战并输出结果接下来把计算结果格式化成对战日志。下面的battle_log函数负责打印伤害和倍率判断def battle_log(result): 打印一次对战结果。 print(f[对战] {result[attacker]} 使用 {result[move_type]} 系招式威力 {result[move_power]}) print(f - 克制倍率{result[multiplier]}本系加成{result[stab]}实际伤害{result[damage]}) print(f 判定{describe_multiplier(result[multiplier])})到这里核心计算能力已经完成。下一节我们把“喵神”“大电海燕”“呆火鳄”“骨纹巨鳄”放入程序中做一次完整的实战演示。5. 完整实战喵神 vs 大电海燕呆火鳄进化前后对比5.1 定义参战宝可梦在battle_simulator.py中我们定义几只宝可梦及其大致的攻防数据。这里的数值只是为了演示代码不代表游戏最终真实面板。from pokemon_data import effect_value, describe_multiplier, ALL_TYPES from pokemon_data import Pokemon, calculate_damage, battle_log def main(): # 参战宝可梦 miao_shen Pokemon(喵神, [草, 恶], base_attack130, base_defense90, level60) da_dian_hai_yan Pokemon(大电海燕, [电, 飞行], base_attack105, base_defense75, level60) dai_huo_e Pokemon(呆火鳄, [火], base_attack65, base_defense50, level35) gu_wen_ju_e Pokemon(骨纹巨鳄, [火, 幽灵], base_attack110, base_defense100, level50)注意Pokemon类是从pokemon_data.py导入的。如果你把所有代码写在一个文件里也可以不导入直接放在同一个文件中。5.2 逆属性斩杀分析我们模拟“喵神”用两种不同的招式攻击“大电海燕”草系招式这是喵神的本系招式但大电海燕是电 飞行属性草系打飞行系有 2 倍克制因此这并不是逆属性。水系招式喵神本身不是水系水系打电海燕的综合倍率是 0.5这才是真正的逆属性。主程序代码如下print( * 60) print(场景一喵神 vs 大电海燕) print( * 60) result_1 calculate_damage(miao_shen, da_dian_hai_yan, move_power80, move_type草) battle_log(result_1) result_2 calculate_damage(miao_shen, da_dian_hai_yan, move_power90, move_type水) battle_log(result_2)预期输出效果类似于[对战] 喵神草/恶 使用 草 系招式威力 80 - 克制倍率2.0本系加成1.5实际伤害320.0 判定克制伤害 [对战] 喵神草/恶 使用 水 系招式威力 90 - 克制倍率0.5本系加成1.0实际伤害60.0 判定逆属性效果不佳这里可以看到即使水系招式威力更高但因为克制倍率低且没有本系加成最终伤害远不如草系招式。这就是属性克制在数值系统中的重要性。5.3 呆火鳄进化前后输出对比呆火鳄进化成骨纹巨鳄之后属性从纯火系变为火 幽灵系基础攻击力也从 65 提升到 110 左右等级也会有所提升。我们让它使用同一个火系招式攻击大电海燕看看伤害变化。print() print( * 60) print(场景二呆火鳄进化前后输出对比) print( * 60) result_3 calculate_damage(dai_huo_e, da_dian_hai_yan, move_power70, move_type火) battle_log(result_3) result_4 calculate_damage(gu_wen_ju_e, da_dian_hai_yan, move_power70, move_type火) battle_log(result_4)预期输出效果类似于[对战] 呆火鳄火 使用 火 系招式威力 70 - 克制倍率1.0本系加成1.5实际伤害68.18 判定普通伤害 [对战] 骨纹巨鳄火/幽灵 使用 火 系招式威力 70 - 克制倍率1.0本系加成1.5实际伤害130.0 判定普通伤害虽然属性克制倍率没有变化但进化之后攻防数值上升伤害明显提升。这正好对应动画剪辑里的“终于进化”带来的力量爆发感。5.4 完整可运行示例最后把main()调用加上形成完整程序。为了便于读者直接运行这里给出合并后的完整文件结构。假设你按两个文件维护在pokemon_data.py中写入第 3 节的代码在battle_simulator.py中写入本节代码最后执行python battle_simulator.py如果你希望单文件运行也可以直接把Pokemon、calculate_damage、battle_log全部复制到battle_simulator.py的前面部分然后运行同一命令。6. 扩展给动漫剪辑生成对战字幕6.1 为什么剪辑需要字幕很多宝可梦动漫剪辑会在高燃对战片段上叠加字幕用来说明招式属性、克制关系、伤害效果。以前这种字幕需要手动在剪辑软件里逐条输入工作效率很低。如果我们已经用 Python 算出了对战结果完全可以让程序自动生成 SRT 字幕文件然后直接拖入剪辑软件使用。6.2 生成 SRT 字幕文件SRT 是最常见的字幕文件格式每一条字幕包含序号、时间码、文本内容。我们可以把刚才的战绩结果统一收集起来生成一个字幕文件def export_srt(results, filenamebattle_subtitle.srt): 将多个对战结果导出为 SRT 字幕文件。 lines [] index 1 for i, result in enumerate(results): start 1 i * 3 end start 2 lines.append(str(index)) lines.append(f00:00:{start:02d},000 -- 00:00:{end:02d},000) text f{result[attacker]} 使用 {result[move_type]} 系招式 desc 效果拔群 if result[multiplier] 1 else (效果不佳… if result[multiplier] 1 else 伤害正常。) lines.append(f{text}{desc}) lines.append() index 1 content \n.join(lines) with open(filename, w, encodingutf-8) as f: f.write(content) print(f字幕文件已生成{filename})调用方式if __name__ __main__: main() # 把四个结果收集起来生成字幕 all_results [result_1, result_2, result_3, result_4] export_srt(all_results)生成的字幕文件会在当前目录下出现。你在剪映、PR、Final Cut 等软件中导入 SRT 文件即可显示。由于这里生成的时间码是简化逻辑实际使用时你还需要根据剪辑节奏调整时间。6.3 与剪辑工作流的结合建议更好的做法是让程序输出 CSV 或 JSON 格式的对战记录然后由剪辑脚本转换成字幕、转场卡点或数据可视化卡片。例如输出 JSON用于前端页面展示双方属性、倍率、伤害。输出 PNG 图片用 Pillow 库生成克制关系说明卡。输出 SRT直接做字幕。本文示例已经覆盖了 SRT 输出后续你可以按需扩展其他格式。7. 常见问题与排查思路7.1 克制倍率计算结果和游戏里不一致可能原因属性克制表写错了比如漏掉某个抵抗关系。目标宝可梦的双属性计算顺序有误。游戏内存在特性影响例如“漂浮”“储水”等会改变属性效果。解决思路先用单属性宝可梦测试例如“火系招式打草系”应该返回 2.0。再测试双属性宝可梦逐步增加复杂度。如果项目要精确还原游戏需要把特性、天气、场地等系统也纳入数据模型。7.2 中文字符在控制台输出乱码这个问题在 Windows 终端比较常见。可以尝试在代码文件开头加一行# -*- coding: utf-8 -*-然后在命令行运行前设置编码chcp 65001如果使用 VS Code把终端编码调整为 UTF-8 也可以解决。7.3 程序报错ValueError: 未知属性这个错误表示招式属性不在ALL_TYPES列表中。检查你传入的move_type是否写错比如把“恶”写成了“暗”或者把“钢”写成了“铁”。属性名必须和克制表里的 key 完全一致。7.4 修改了宝可梦数据但运行结果没变如果程序已经运行过一次并且使用了旧的.pyc缓存在某些编辑器里可能不会立即生效。更常见的原因是你修改了pokemon_data.py但battle_simulator.py中导入的还是旧对象。可以尝试删除__pycache__目录后重新运行rm -rf __pycache__7.5 生成的 SRT 字幕导入剪辑软件后时间不对SRT 时间码中的小时、分钟、秒、毫秒必须严格使用英文冒号和逗号分隔。如果剪辑软件要求毫秒必须 3 位请确保格式化时补 0。本文示例用00:00:01,000的形式满足规范但实际剪辑时你需要根据视频片段重新调整时间码。8. 最佳实践与工程建议8.1 用数据驱动代替硬编码属性克制表和宝可梦数据最好单独放在一个模块中甚至抽成 JSON 文件。这样以后想新增属性、修改倍率、添加新宝可梦都不需要改动核心计算函数。例如把克制表导出为 JSONimport json with open(type_chart.json, w, encodingutf-8) as f: json.dump(TYPE_EFFECTIVENESS, f, ensure_asciiFalse, indent2)然后在程序中读取 JSON 文件实现真正的数据驱动。8.2 把计算逻辑和输出逻辑分离calculate_damage应该只负责返回计算结果字典不应该在内部直接print。battle_log才负责展示。这样如果未来要改成 Web API只需要调用calculate_damage并序列化结果不需要修改任何计算代码。8.3 给核心函数补充测试属性克制计算是规则型逻辑非常适合做单元测试。你可以用 Python 自带的unittest写几个固定案例import unittest class TestEffectValue(unittest.TestCase): def test_fire_vs_grass(self): self.assertEqual(effect_value(火, [草]), 2.0) def test_ground_vs_flying(self): self.assertEqual(effect_value(地面, [飞行]), 0.0) def test_double_type(self): self.assertEqual(effect_value(地面, [电, 飞行]), 0.0) if __name__ __main__: unittest.main()这样每次修改克制表后只要运行测试就能快速发现数据错误。8.4 注意异常边界在编写规则引擎时至少要考虑这些边界情况攻击属性不存在。防御属性列表为空。招式威力为 0 或负数。攻防数值为 0。在calculate_damage中已经对未知属性做了异常处理建议再补充威力校验if move_power 0: raise ValueError(招式威力必须大于 0)8.5 与内容创作工具结合时要尊重版权如果你把这段代码用于制作宝可梦动漫剪辑、二创视频请注意遵守平台版权规则和创作者伦理。技术工具可以提升效率但内容发布前要确保获得合法授权避免侵权行为。涉及素材、数据、图片的自动化采集与生成务必在授权范围内使用遵循最小必要原则。9. 总结与学习路线这个项目从“喵神逆属性斩杀大电海燕”的动画剪辑切入完成了三件事建了一张完整的属性克制表实现了克制倍率和伤害计算并用呆火鳄进化前后的数据对比展示了进化带来的数值提升。最后还扩展了 SRT 字幕导出让计算结果可以直接服务于剪辑工作流。代码本身不算复杂但麻雀虽小五脏俱全至少涉及 Python 的字典、列表、类、函数、文件读写和异常处理。建议你按下面的顺序继续深入把属性克制表改成外部 JSON 文件并实现加载逻辑。给宝可梦增加特性、道具、天气、场地等更多伤害修正参数。把纯命令行程序改造成 Flask Web API通过 POST 请求返回对战结果。用前端页面展示双方属性、倍率和伤害数值。尝试接入真实宝可梦图鉴数据使用爬虫或开放接口获取更完整的图鉴信息。把这个模拟器推广到其他规则型业务中比如数值策划验证、游戏平衡性测试、规则引擎设计。如果你也遇到过“动画里逆属性斩杀到底怎么算”的疑问现在不妨打开编辑器把代码跑一遍。你可以把喵神、大电海燕、呆火鳄、骨纹巨鳄的数据换成自己感兴趣的宝可梦体验一手打造数值模拟器的乐趣。动手实践比背一百遍克制表更有效。
返回列表