- 教育
- CLI
【免费下载链接】warriorjs
🏰 An exciting game of programming and Artificial Intelligence
WarriorJS 是一款"编程与人工智能"回合制游戏,勇士(Warrior)的最终目标不只是到达楼梯,而是在抵达楼梯的同时尽可能获得最高分。本文以官方玩家文档 docs/player/scoring.md 为骨架,结合libs/scoring、libs/core等源码与测试,完整拆解 WarriorJS 的计分规则:哪些行为加分、哪些行为扣分、总分如何由三部分构成、等级(Grade)如何评定,并给出可落地的高分实战策略。
一句话总结计分规则
你的目标是不仅到达楼梯,还要获得你能获得的最高分。每个关卡都有多种得分途径,也有可能导致扣分的危险行为;通关后本关得分累加到你的总分(total score)中,随你在整座塔(Tower)中的推进不断累积。
一、得分来源(Earning Points)
根据官方文档,一个关卡内你可以在以下四类行为中获得分数:
| 行为 | 得分规则 |
|---|---|
| 击败敌人(Defeat an enemy) | 将该敌人的**最大生命值(max health)**加入你的得分 |
| 营救俘虏(Rescue a captive) | 获得该俘虏对应的奖励分(reward) |
| 在奖励时间内通过关卡(Pass the level within the bonus time) | 获得剩余奖励时间作为分数(每关的奖励时间逐回合递减) |
| 击败所有敌人并营救所有俘虏(Defeat all enemies and rescue all captives) | 获得20% 整体奖励(20% overall bonus) |
1. 击败敌人:得分 = 敌人的最大生命值
"击败敌人得分等于其最大生命值"这一规则在源码中有明确体现:libs/core/src/Unit.ts中定义了所有单位通用的reward属性,其默认值就是最大生命值:
get reward(): number { return this.maxHealth; }也就是说,普通敌人的"身价"与其最大生命值直接挂钩——血越厚的敌人,击败它获得的分越多。以当前仓库libs/units/src中的单位为例:
- Sludge:
maxHealth = 12,击败得 12 分; - Thick Sludge:
maxHealth = 24,击败得 24 分; - Archer:
maxHealth = 7,击败得 7 分; - Wizard:
maxHealth = 3,击败得 3 分。
具体加分的时机由Unit.damage()决定(见 libs/core/src/Unit.ts):当攻击把目标打死后,若目标是敌人(enemy),攻击者earnPoints(receiver.reward)加分;对应测试见 libs/core/src/Unit.test.ts。
2. 营救俘虏:获得固定奖励分
俘虏(Captive)是特殊的友方单位,营救它可以获得奖励。注意:俘虏的reward被覆写为固定的 20 分,与其极低的maxHealth(1 点)无关,见 libs/units/src/Captive.ts:
class Captive extends Unit { readonly name = 'Captive'; readonly maxHealth = 1; readonly enemy = false; override bound = true; override get reward(): number { return 20; } }营救动作由Unit.release()实现(libs/core/src/Unit.ts):对非敌人单位,先使其vanish(),然后earnPoints(receiver.reward)。对应测试 libs/core/src/Unit.test.ts 验证了"营救友方单位可获得与 reward 等值的分数"。在战斗中触发营救的是 Rescue 能力,调用warrior.rescue()即可释放前方被束缚的单位。
3. 时间奖励:奖励时间逐回合递减
每个关卡都有一个初始时间奖励(time bonus),它随你行动的每个回合递减。通关时,剩余的时间奖励全部计入得分。
源码实现见 libs/scoring/src/getRemainingTimeBonus.ts:
function getRemainingTimeBonus(turns: ScoringEvent[][], timeBonus: number): number { const turnCount = getTurnCount(turns); const remainingTimeBonus = timeBonus - turnCount; return Math.max(remainingTimeBonus, 0); }其中getTurnCount就是turns.length(libs/scoring/src/getTurnCount.ts),即你实际玩了多少个回合。因此:
- 使用回合越少,剩余时间奖励越多;
- 若使用回合数超过初始 time bonus,该项为 0(不会扣成负数,
Math.max(..., 0)保证了下限)。
对应测试见 libs/scoring/src/getRemainingTimeBonus.test.ts:初始 10、用了 3 回合得 7 分;用了 11 回合则归 0。
关卡配置:timeBonus是关卡配置项(见 libs/core/src/types.ts 中的LevelConfig)。以官方塔 towers/the-narrow-path/src/index.ts 为例,第 1 关timeBonus: 15,第 2 关timeBonus: 20,第 3 关timeBonus: 35,第 4 关timeBonus: 45——随着难度递进,时间奖励也更充裕。
4. 全清奖励:20% 整体奖励
若你击败了所有敌人并营救出所有俘虏(即关卡被完全清理,地板上除勇士外没有任何单位),将获得一份相当于"勇士得分 + 时间奖励"之和 20% 的整体奖励。
源码实现见 libs/scoring/src/getClearBonus.ts:
function getClearBonus(turns: ScoringEvent[][], warriorScore: number, timeBonus: number): number { const lastEvent = getLastEvent(turns); if (!isFloorClear(lastEvent.floorMap)) { return 0; } return Math.round((warriorScore + timeBonus) * 0.2); }关键点:
- 清理判定:
isFloorClear(libs/scoring/src/isFloorClear.ts)统计最终回合地图上除勇士外的单位数量,unitCount <= 1(只允许勇士自己)才算清理完成; - 计算基准:
(勇士得分 + 时间奖励) × 0.2,结果四舍五入取整(Math.round); - 未清理则该项为 0,即使你照样走到了楼梯、通过了关卡,也拿不到这 20%。
测试验证见 libs/scoring/src/getClearBonus.test.ts:例如勇士得分 3、时间奖励 2、关卡已清理,则Math.round((3+2)*0.2) = 1。
二、扣分来源(Losing Points)
文档同时提醒你必须小心:杀死一名俘虏(kill a captive)会招来惩罚。
扣分的机制与加分共用同一套Unit.damage()逻辑(libs/core/src/Unit.ts):当你的攻击杀死一个目标时,源码会检查目标isEnemy()——
- 目标是敌人:
this.earnPoints(receiver.reward),加分; - 目标不是敌人(即友方,例如俘虏):
this.losePoints(receiver.reward),减去等值分数,也就是惩罚。
对应测试见 libs/core/src/Unit.test.ts:击杀友方单位会losePoints(10)。落实到实战中,俘虏maxHealth只有 1 且处于束缚状态(bound = true),任何一次攻击都可能误杀它,从而损失 20 分并失去后续的营救奖励,因此开战前务必用warrior.feel().getUnit()?.isEnemy()确认目标身份。
三、总分构成:getLevelScore 的三段式结构
通关后,本关得分由 libs/scoring/src/getLevelScore.ts 统一汇总,返回一个包含三个组成部分的对象:
function getLevelScore({ passed, turns }: LevelResult, { timeBonus }: LevelConfig): LevelScore | null { if (!passed) { return null; } const warriorScore = getWarriorScore(turns); const remainingTimeBonus = getRemainingTimeBonus(turns, timeBonus); const clearBonus = getClearBonus(turns, warriorScore, remainingTimeBonus); return { clearBonus, timeBonus: remainingTimeBonus, warrior: warriorScore, }; }| 字段 | 含义 | 来源 |
|---|---|---|
warrior | 勇士在本关累计的击杀/营救净得分 | getWarriorScore.ts 读取最后一回合事件的warriorStatus.score |
timeBonus | 剩余时间奖励(timeBonus - 回合数,最低 0) | getRemainingTimeBonus.ts |
clearBonus | 全清 20% 奖励(未全清为 0) | getClearBonus.ts |
勇士得分的来源
getWarriorScore的实现非常直白:从最后一回合的最后一个事件中读取勇士状态里的score字段(libs/scoring/src/getWarriorScore.ts、libs/scoring/src/getLastEvent.ts)。这个score由Warrior实时维护:Unit.earnPoints()做this.score += points,losePoints()做this.score -= points(libs/core/src/Unit.ts),并通过 Warrior.getStatus() 暴露{ health, score }供事件记录。也就是说,你每一回合的击杀加分、误杀扣分都会实时累计进warriorStatus.score。
总分的合成与等级评定
在 CLI 层,apps/cli/src/utils/buildLevelReport.ts 将三部分相加得到本关总分,并依据关卡配置的aceScore计算等级:
const scoreParts = getLevelScore(levelResult, { timeBonus: levelConfig.timeBonus ?? 0 })!; const totalScore = scoreParts.warrior + scoreParts.timeBonus + scoreParts.clearBonus; const grade = levelConfig.aceScore ? totalScore / levelConfig.aceScore : 0;- 总分=
warrior+timeBonus+clearBonus; - 等级(grade)= 总分 ÷
aceScore(每个关卡配置的"满分基准",见 libs/core/src/types.ts)。
等级字母由 libs/scoring/src/getGradeLetter.ts 映射(对应测试见 getGradeLetter.test.ts):
| 等级数值 | 字母 |
|---|---|
>= 1.0 | S |
>= 0.9 | A |
>= 0.8 | B |
>= 0.7 | C |
>= 0.6 | D |
< 0.6 | F |
等级会在结算界面展示(apps/cli/src/ui/components/ResultScreen/ResultScreen.tsx),并在史诗模式(Epic Mode)中用于计算平均等级与史诗分数(见 apps/cli/src/Profile.ts 的tallyPoints、calculateAverageGrade、getEpicScoreWithGrade)。
四、实战高分策略
综合文档规则与源码逻辑,最大化总分可以归纳为四条核心策略:
少回合、快通关,吃满时间奖励:时间奖励随回合递减,这是"看得见"的分数。每回合只做一件有意义的事(WarriorJS 每回合只能执行一个动作),提前规划好行走、攻击、休息的节奏,避免无意义的空走与反复试探。通关回合数 ≥ timeBonus 时该项归 0,且不影响其他两项,所以时间奖励是"锦上添花"项,但往往决定你能否达到 S 级。
击败每一个敌人,且只击杀敌人:敌人身价 = 最大生命值,血厚的 Thick Sludge(24 分)尤其值得优先击杀;而俘虏只有 1 点血,一个误击就会
losePoints(20)——既丢了分,又失去了营救奖励和全清资格,双重损失。别漏掉任何俘虏:每个俘虏固定 20 分,且是触发全清奖励的必要条件之一。用
isEnemy()/isBound()识别并rescue(),不要用攻击去"清场"。追求全清,拿到 20% 整体奖励:全清奖励 =
(勇士得分 + 时间奖励) × 20%,它是基于前两项的乘法型加成——你击杀越多、通关越快,这个 20% 的绝对值就越大。这意味着"绕开敌人直奔楼梯"虽然能通过关卡,却会损失掉全清奖励这最后一块拼图。
以 towers/the-narrow-path/src/index.ts 第 1 关为例(timeBonus: 15、aceScore: 10):假设你 3 回合内击杀 1 只 Sludge 并全清通关,则勇士得分 12、时间奖励 12、全清奖励Math.round(24 × 0.2) = 5,总分 29,等级29 / 10 = 2.9,轻松拿下 S 级。反过来,如果拖到 16 回合以上且漏掉全清,总分会显著缩水。
五、小结
WarriorJS 的计分体系清晰而克制:加分项(击杀、营救、时间奖励、全清 20% 加成)与扣分项(误杀俘虏)共同决定了你的单关得分;总分 = 勇士得分 + 时间奖励 + 全清奖励,除以关卡aceScore得到 S/A/B/C/D/F 等级。理解这套规则后,最优解不再是"莽到楼梯",而是"快速、精准、零误伤地清理整个关卡"——这正是 WarriorJS 鼓励玩家打磨 AI 决策逻辑的核心所在。相关实现与测试均可在仓库的 libs/scoring 与 libs/core/src/Unit.ts 中直接查阅验证。
- 教育
- CLI
【免费下载链接】warriorjs
🏰 An exciting game of programming and Artificial Intelligence
相关推荐
Lighthouse 评分机制完全解读:Performance、Best Practices、SEO 与 Accessibility 得分究竟如何计算
Lighthouse 评分机制完全解读:Performance、Best Practices、SEO 与 Accessibility 得分究竟如何计算 Ligh
开发工具测试性能测试前端OpenProject 项目属性(Project Attributes)完全指南:创建、分组、格式与公式计算
OpenProject 项目属性(Project Attributes)完全指南:创建、分组、格式与公式计算 本文基于 OpenProject 官方系统管理员文
后端前端项目管理企业应用协同办公SoapUI终极指南:如何用免费开源工具轻松搞定API测试
SoapUI终极指南:如何用免费开源工具轻松搞定API测试 你是否曾为复杂的API测试而烦恼?面对REST、SOAP、GraphQL等各种接口协议,需要一个强大
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考