Balatro后端进阶(3):为什么机制设计比写代码更难 本系列记录我从 0 到 1 实现一个 Balatro 风格的游戏后端系统并逐步将其工程化为可扩展的实时服务項目。该项目的代码已开源可在 GitHub 与 GitCode 上获取。 这篇文章原本不在计划里故无对应分支代码。⚠️ 本文是 Balatro 后端項目的第 3 篇进阶分支写在主分支第 8 篇正式实现之前。写在前面这篇文章有点特殊。它不是一次功能实现也不是一次代码提交。而是我在准备第 8 篇“跳过 Blind 与 Tag 奖励机制设计”时突然卡住后写下来的记录。卡住的原因也不是代码不会写而是我第一次很明显地感觉到机制设计好像真的比写代码更难。⚠️这次到底卡在了哪里卡住之后我才发现自己真正纠结的并不是skipBlind这个方法怎么写。而是它背后的一连串问题玩家为什么愿意跳过当前的Blind跳过后应该给什么奖励太强会不会破坏平衡奖励太弱玩家为什么要选这个奖励是立即生效还是延迟生效这个状态应该存在当前Blind还是跨Blind保留后端应该如何记录这个选择带来的影响这些问题看起来都围绕一个“跳过 Blind”的功能展开。但真正想下去以后我才发现它其实已经不是单纯的代码实现问题了。所以本篇不展开第 8 篇的正式实现只记录我在写“跳过 Blind 与 Tag 奖励机制设计”之前对机制设计、状态流转和項目演化一次思考。文章目录⚠️这次到底卡在了哪里一、以前更多是在明确需求下写代码1. 以前的边界是别人定义好的2. 我更熟悉的是“需求 - 参数 - 代码”二、但 Balatro 項目没有人替我定义需求1. 个人项目开始要求自己判断2. 从单次行为进入生命周期三、真正难的是生命週期而不是某一段代码1. 问题不再只是一个函数怎么写2. 思考顺序发生了变化四、为什么不能只是照着游戏抄一遍1. 结果可以查到但原因需要理解2. 真正难的是风险和收益五、从“实现功能”到“设计机制”1. 第一阶段更偏功能实现2. 第二阶段开始偏规则设计六、为什么有些数据一定要由后端处理1. 不是因为重要而是因为会影响结果2. 前端展示后端维护可信规则七、从“我学到了什么”变成“我要怎么讲清楚”1. 从记录学习到说清设计过程八、这件事对我有什么用1. AI 能帮我写代码但不能替我经历项目演化九、从查攻略到理解问题本质1. 有些问题不是知道答案就结束了十、总结1. 这次卡住卡的不是代码2. 从“实现需求”到“理解规则”3. 记录这次卡住一、以前更多是在明确需求下写代码1. 以前的边界是别人定义好的在过往的工作经历中我处于的角色更多是产品或者項目经理给出具体需求文檔然后我根据一个明确的需求去完成对应实现。这个流程其实是比较清楚的。比如这个接口需要什么参数返回什么数据什么情况下报错什么情况下算成功前端需要什么字段测试应该怎么验收在这种模式下我更多思考的是这个需求怎么实现比较合理而不是为什么要这样设计这个需求这并不是说以前的开发不需要思考。只是以前大部分时候问题的边界已经被别人定义好了。我需要在一个相对明确的范围内把代码写出来把逻辑跑通把异常处理好。2. 我更熟悉的是“需求 - 参数 - 代码”也就是说过去更多的是需求 - 参数 - 代码先有需求再拆参数最后落到代码实现。这个过程我相对熟悉。尤其我过去做的大多也是游戏向开发很多逻辑说难不难说简单也不简单但需求通常是明确的。我只需要根据已有的规则去完成对应的逻辑实现就好。二、但 Balatro 項目没有人替我定义需求1. 个人项目开始要求自己判断而Balatro这个項目不太一样它是属于我个人的。没有項目经理给我写需求文檔也没有同事和我一起做思路碰撞。很多事情都需要自己去判断这个阶段应该做什么当前功能应该拆到什么程度哪些状态应该由后端维护哪些逻辑现在可以简化哪些结构要为以后扩展留口子这个机制到底解决了什么问题一开始做这个項目的时候其实没有这么累。因为前面的功能都比较直观。比如发牌、出牌、弃牌、补牌、判断牌型、计算分数、判断当局是否结束。这些都属于局内行为。它们的特点是执行一次就结束一次。比如玩家出牌后端计算这次出牌的分数然后从手牌中移除对应卡牌再补牌。这个过程虽然也需要状态管理但它的边界比较清楚。可以简单理解成玩家出牌 ↓ 校验选择的卡牌 ↓ 计算当前牌型分数 ↓ 更新当前 Blind 得分 ↓ 扣除出牌次数 ↓ 补充玩家手牌这条链路虽然也有状态变化但整体还是围绕“一次玩家行为”展开。2. 从单次行为进入生命周期可到了第二阶段事情就开始变得不一样了。因为項目开始进入了Blind、Ante、Boss Blind、跳过奖励、阶段推进这些内容。阶段我主要在做什么难点第一阶段发牌、出牌、补牌、算分把单次行为跑通第二阶段Blind、Ante、Boss、跳过奖励判断状态如何流转后续阶段商店、特殊卡牌、Modifier让系统可以持续扩展也就是从“单次行为”进入到了“生命週期”。三、真正难的是生命週期而不是某一段代码1. 问题不再只是一个函数怎么写第二阶段之后我遇到的问题开始变成当前Blind是谁当前Ante是多少当前目标分是多少玩家什么时候进入下一个Blind什么时候进入Boss Blind什么时候重置状态哪些状态只属于当前Blind哪些状态需要跨Blind保留哪些状态未来可能需要持久化这些问题并不是单纯靠写一个函数就能解决因为它们会影响整个游戏流程。以前写一个出牌逻辑我可以想玩家选择卡牌 - 判断牌型 - 计算分数 - 扣除出牌次数 - 补牌这个链路是相对性短的。但现在设计Blind流转时我需要想的是当前Blind的状态 - 玩家行为 - 分数变化 - 是否达标 - 是否推进Blind- 是否进入Boss- 是否重置局内状态 - 是否保留进度这就不再只是一个函数的问题了它开始变成状态之间的关系。2. 思考顺序发生了变化也正是在这里我对項目的理解发生了一些变化。以前我更习惯需求 - 参数 - 代码而现在变成了规则 - 状态 - 生命周期 - 架构 - 可扩展性可以简单理解成我现在遇到的不是“代码变多了”而是思考顺序变了。过去的开发方式现在的項目状态需求已经明确规则需要自己判断参数边界清楚状态归属需要设计按需求完成实现需要考虑生命周期验收标准由别人定义对错需要自己权衡重点是代码能否跑通重点是系统能否继续扩展四、为什么不能只是照着游戏抄一遍在写“跳过Blind与Tag奖励机制设计”的时候我其实对自己产生过怀疑。1. 结果可以查到但原因需要理解因为Balatro是一个公开的游戏游戏能下載机制能看到攻略也能查到。比如跳过Blind会给什么奖励游戏里有哪些Tag每个Tag大概有什么效果这些内容其实都可以通过查资料或者打开游戏获得。所以我有一个疑问既然结果都能看到那我为什么还要花这么多时间去思考“为什么”直接照着实现不就好了么但深入了解后我才发现问题并不是“知不知道游戏里有哪些Tag”。真正的问题是我是否理解这个机制为什么存在。比如跳过Blind这个行为。如果只看表面它可能只是一个选项玩家选择跳过 - 后端发放一个奖励。但如果继续往下想它其实在解决一个更深的问题玩家放弃当前Blind收益换取一个不确定的未来收益。2. 真正难的是风险和收益这就涉及到了风险和收益的问题如果奖励太弱玩家没有理由跳过如果奖励太强跳过就会变成最优解反而破坏正常流程如果奖励立即生效可能影响当前阶段如果奖励延迟生效就需要后端记录它何时触发如果奖励会影响商店、经济系统、卡牌成长那么它又会和未来很多系统产生关联所以我真正需要理解的不是游戏里具体给了什么奖励而是为什么这个奖励适合放在跳过Blind之后。这两件事是不一样的。前者是查资料后者是做设计。这次真正让我卡住的也并不是“查不到答案”。不是而是不知道Tag有哪些不知道为什么这个机制适合放在这里不知道跳过Blind给什么不知道奖励强弱如何影响选择不知道接口怎么写不知道状态应该怎么保留不知道代码怎么跑不知道规则如何进入系统五、从“实现功能”到“设计机制”这次卡住也让我重新看了这个項目从开始到现在的变化。1. 第一阶段更偏功能实现第一阶段我更多是在完成一个个功能点。比如我会问怎么判断牌型怎么洗牌怎么发牌怎么扣除玩家手牌怎么计算当前出牌分数这些问题都比较偏实现。2. 第二阶段开始偏规则设计到了第二阶段我开始问的是这个状态属于谁它什么时候创建它什么时候销毁它是否应该跨阶段保留如果以后加数据库它应该如何持久化如果以后加商店系统它要不要和商店发生关系如果以后加特殊奖励它要不要抽象成统一的效果系统这些问题开始偏设计。我也慢慢意识到代码不是一开始就复杂的复杂是因为状态之间开始产生关系。当項目只有发牌、出牌、结算时它就像是一条线。但当項目开始出现Blind、Ante、Boss、跳过奖励、阶段流转时它就开始像一个系统。 而系统最难的地方不是某一个函数怎么写而是每个状态应该放在哪里。六、为什么有些数据一定要由后端处理在这个过程中我也对一些以前模糊的概念有了更具体的理解。比如为什么有些数据一定要由后端处理1. 不是因为重要而是因为会影响结果以前我可能会比较直觉地觉得这个数据重要所以放后端。但现在我会更倾向于从状态和规则的角度去判断。比如Blind当前进度、目标分、玩家是否跳过、获得了什么Tag这些都不是单纯展示用的数据它们会影响后续流程。只要一个数据会影响游戏结果那么它就不应该只依赖前端维护。因为前端更适合展示和交互而后端需要负责规则判断和状态可信。2. 前端展示后端维护可信规则这里暂时不讨论具体实现只从职责边界上做一个简单区分前端适合做展示当前Blind展示目标分展示可选择的Tag展示玩家当前状态后端应该负责判断 Blind 是否可跳过判断 Tag 是否可发放判断Tag是否已生效判断当前状态是否能进入下一阶段维护可信的游戏进度这也是我在Balatro項目中逐渐明确的一点后端不只是返回数据而是维护规则的可信来源。这句话以前可能也听过但真正自己做項目去思考时才会开始理解它为什么重要。七、从“我学到了什么”变成“我要怎么讲清楚”1. 从记录学习到说清设计过程除了代码本身这个項目对我来说另一个影响是“输出”。最开始写博客的时候我更多是在记录我今天学到了什么。比如我学会了NestJS学会了怎么写测试。但现在不太一样了。现在每一篇主分支博文我都会需要先想这一篇到底解决什么问题为什么它应该放在当前阶段它和前一篇有什么关系它会为后一篇留下什么基础我怎么把自己的设计过程说清楚这就导致我写文章的方式也变了。以前可能是边做边写。现在更多的是先想清楚流程再写代码再回头整理文章。有时候这比写代码还累。因为写代码只需要跑通了至少能看到结果。但设计思路有没有说清楚很多时候没那么直观。这也是我现在觉得难的地方。八、这件事对我有什么用1. AI 能帮我写代码但不能替我经历项目演化在写第八篇之前我确实问过自己我为什么要自己做奖励机制设计这对我的成长到底有什么用尤其现在 AI 工具这么多。Cursor、Copilot、Claude、ChatGPT、CodeX都是能帮程序员更快的生成代码。单纯写代码的能力越来越不稀缺了。但这段时间做Balatro項目后我慢慢有了一点新的理解。AI 可以提高代码生产效率也可以辅助我拆解问题、Review 设计、指出边界情况。 但它不能替我经历一个項目从简单功能逐渐演化成复杂系统的过程。它可以告诉我一个函数怎么写但我需要自己去判断这个函数为什么存在这个状态属于哪个模块这个规则未来是否会扩展当前是否应该抽象什么时候应该先简单实现什么时候必须提前拆边界这些东西不是单纯看教程、查攻略就能完全变成自己的知识储备。它需要在一个真实的項目里反复遇到问题再反复调整。九、从查攻略到理解问题本质回头看这个項目从初始到现在我能感觉到自己的变化。一开始遇到问题时我更习惯查攻略、看答案、找别人怎么做。这当然有用。1. 有些问题不是知道答案就结束了但做到现在我开始发现有些问题不是知道答案就结束了。比如为什么这个状态应该放在后端为什么这里需要生命周期为什么跳过Blind不能只是一个简单接口为什么奖励机制会影响后续架构为什么一个小功能会牵扯到未来扩展这些问题没有唯一标准答案或者说即使有现成答案我也需要理解它背后的原因。因为我不是在做一道题。我是在做一个会不断演化的項目这也是Balatro項目对我来说越来越重要的地方。它不只是一个游戏后端deDemo, 它更像是一个可以让我练习架构判断、状态设计、工程拆分和技术表达的长期項目。十、总结1. 这次卡住卡的不是代码这篇文章不是第 8 篇的正式实现它只是我在准备写“跳过Blind与Tag奖励机制设计”之前突然卡住后写下来的一次记录。但也正是因为这次卡住我才意识到自己现在遇到的难点已经不只是“代码怎么写”了。2. 从“实现需求”到“理解规则”以前更多是别人把需求定义好我在明确的范围里完成实现。而现在这个項目是我自己的。没有人提前告诉我这个机制应该怎么拆状态应该放在哪里生命週期应该怎么走。以前的“对错”很多时候是产品经理或者需求文檔定义的。而现在在自己的項目里我需要通过规则、状态、生命週期和扩展性去判断一个设计是否合理。这件事确实更累… 因为写代码的时候至少还能通过运行结果判断它有没有跑通。但机制设计很多时候没有一个立刻可见的答案只能不断想、不断推翻、不断调整。3. 记录这次卡住也许这就是这个項目对我来说真正有意义的地方。让自己不只停留在“接需求、改老代码、修线上问题”的状态里。这篇没有什么标准答案只是突然的想记录一下。记录这次卡住也记录自己开始从“实现需求”慢慢走向“理解规则”的过程。