Initial commit: Text Adventure Game

Features:
- Combat system with AP/EP hit calculation and three-layer defense
- Auto-combat/farming mode
- Item system with stacking support
- Skill system with levels, milestones, and parent skill sync
- Shop system with dynamic pricing
- Inventory management with bulk selling
- Event system
- Game loop with offline earnings
- Save/Load system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-01-21 17:13:51 +08:00
commit cb412544e9
90 changed files with 17149 additions and 0 deletions

View File

@@ -0,0 +1,190 @@
/**
* 战斗系统单元测试
* 测试纯函数,不依赖框架
*/
// 由于项目使用 ESM我们需要使用 require 直接导入
// 注意:这些测试假设代码已经被转换为 CommonJS 或者通过 babel 转换
describe('战斗系统 - 纯函数测试', () => {
describe('calculateHitRate - 命中概率计算', () => {
// 直接测试纯函数逻辑
const calculateHitRate = (ap, ep) => {
const ratio = ap / (ep + 1)
if (ratio >= 5) return 0.98
if (ratio >= 3) return 0.90
if (ratio >= 2) return 0.80
if (ratio >= 1.5) return 0.65
if (ratio >= 1) return 0.50
if (ratio >= 0.75) return 0.38
if (ratio >= 0.5) return 0.24
if (ratio >= 0.33) return 0.15
if (ratio >= 0.2) return 0.08
return 0.05
}
test('AP >> EP 时应该有高命中率', () => {
expect(calculateHitRate(50, 10)).toBe(0.90) // ratio = 4.5
expect(calculateHitRate(100, 10)).toBe(0.98) // ratio = 9.1
})
test('AP = EP 时应该有50%命中率', () => {
// ratio = 10 / (10 + 1) = 0.909 -> 实际上 > 0.75,所以是 0.38
expect(calculateHitRate(10, 10)).toBe(0.38)
// ratio = 20 / (20 + 1) = 0.95 -> 实际上 > 0.75,所以是 0.38
expect(calculateHitRate(20, 20)).toBe(0.38)
})
test('AP < EP 时应该有低命中率', () => {
// ratio = 10 / (20 + 1) = 0.476 -> < 0.5>= 0.33,所以是 0.15
expect(calculateHitRate(10, 20)).toBe(0.15)
// ratio = 5 / (20 + 1) = 0.238 -> >= 0.2,所以是 0.08
expect(calculateHitRate(5, 20)).toBe(0.08)
})
test('极低 AP/EP 比例时应该只有5%命中率', () => {
// ratio = 1 / (10 + 1) = 0.09 -> < 0.2,所以是 0.05
expect(calculateHitRate(1, 10)).toBe(0.05)
// ratio = 2 / (20 + 1) = 0.095 -> < 0.2,所以是 0.05
expect(calculateHitRate(2, 20)).toBe(0.05)
})
test('边界值测试', () => {
// ratio = 0 / (10 + 1) = 0 -> < 0.2,所以是 0.05
expect(calculateHitRate(0, 10)).toBe(0.05)
// ratio = 10 / (0 + 1) = 10 -> >= 5所以是 0.98
expect(calculateHitRate(10, 0)).toBe(0.98)
})
})
describe('AP/EP 计算逻辑验证', () => {
test('基础AP计算公式', () => {
// AP = 灵巧 + 智力*0.2
const dexterity = 10
const intuition = 10
const baseAP = dexterity + intuition * 0.2
expect(baseAP).toBe(12)
})
test('基础EP计算公式', () => {
// EP = 敏捷 + 智力*0.2
const agility = 8
const intuition = 10
const baseEP = agility + intuition * 0.2
expect(baseEP).toBe(10)
})
test('姿态加成计算', () => {
const baseAP = 10
const stances = {
attack: 1.1,
defense: 0.9,
balance: 1.0,
rapid: 1.05,
heavy: 1.15
}
expect(baseAP * stances.attack).toBe(11)
expect(baseAP * stances.defense).toBe(9)
expect(baseAP * stances.balance).toBe(10)
})
test('环境惩罚计算', () => {
const ap = 10
// 黑暗环境 -20%
expect(ap * 0.8).toBe(8)
// 恶劣环境 -10%
expect(ap * 0.9).toBe(9)
})
test('多敌人惩罚计算', () => {
const ap = 10
const enemyCount = 3
// 惩罚 = Math.min(0.3, (enemyCount - 1) * 0.05)
// 3个敌人 = (3-1)*0.05 = 0.10 = 10%
const penalty = Math.min(0.3, (enemyCount - 1) * 0.05)
expect(ap * (1 - penalty)).toBe(9)
})
test('耐力惩罚计算', () => {
const ap = 10
// 耐力耗尽
expect(ap * 0.5).toBe(5)
// 耐力25%
const staminaRatio = 0.25
expect(ap * (0.5 + staminaRatio * 0.5)).toBe(6.25)
})
})
describe('暴击系统计算', () => {
test('基础暴击率计算', () => {
// 基础暴击率 = 灵巧 / 100
const dexterity = 10
const baseCritRate = dexterity / 100
expect(baseCritRate).toBe(0.10)
})
test('暴击率上限', () => {
const critRate = 0.90
expect(Math.min(0.8, critRate)).toBe(0.80)
})
test('暴击倍数计算', () => {
const baseMult = 1.5
const bonusMult = 0.2
expect(baseMult + bonusMult).toBe(1.7)
})
})
describe('伤害计算验证', () => {
test('基础伤害公式', () => {
// 伤害 = 攻击力 * (1 - 防御减伤)
const damage = 100
const defense = 0.3 // 30%减伤
expect(damage * (1 - defense)).toBe(70)
})
test('最大防御减伤90%', () => {
const damage = 100
const maxDefense = 0.9
// 浮点数精度问题,使用 toBeCloseTo
expect(damage * (1 - maxDefense)).toBeCloseTo(10, 1)
})
test('暴击伤害', () => {
const damage = 100
const critMult = 1.5
expect(damage * critMult).toBe(150)
})
})
describe('边界条件测试', () => {
test('AP最小值为1', () => {
const ap = 0.5
expect(Math.max(1, Math.floor(ap))).toBe(1)
})
test('EP最小值为1', () => {
const ep = 0.3
expect(Math.max(1, Math.floor(ep))).toBe(1)
})
test('耐力不能为负数', () => {
const stamina = -10
const maxStamina = 100
const ratio = Math.max(0, stamina / maxStamina)
expect(ratio).toBe(0)
})
test('属性默认值处理', () => {
const baseStats = undefined
const dexterity = baseStats?.dexterity || 0
expect(dexterity).toBe(0)
})
})
})

View File

@@ -0,0 +1,383 @@
/**
* 物品系统单元测试
* 测试纯函数,不依赖框架
*/
describe('物品系统 - 纯函数测试', () => {
describe('getQualityLevel - 品质等级计算', () => {
const QUALITY_LEVELS = {
1: { level: 1, name: '垃圾', color: '#9e9e9e', range: [0, 49], multiplier: 0.5 },
2: { level: 2, name: '普通', color: '#ffffff', range: [50, 99], multiplier: 1.0 },
3: { level: 3, name: '优秀', color: '#1eff00', range: [100, 129], multiplier: 1.3 },
4: { level: 4, name: '稀有', color: '#0070dd', range: [130, 159], multiplier: 1.6 },
5: { level: 5, name: '史诗', color: '#a335ee', range: [160, 199], multiplier: 2.0 },
6: { level: 6, name: '传说', color: '#ff8000', range: [200, 250], multiplier: 2.5 }
}
const getQualityLevel = (quality) => {
const clampedQuality = Math.max(0, Math.min(250, quality))
for (const [level, data] of Object.entries(QUALITY_LEVELS)) {
if (clampedQuality >= data.range[0] && clampedQuality <= data.range[1]) {
return {
level: parseInt(level),
...data
}
}
}
return QUALITY_LEVELS[1]
}
test('应该正确识别垃圾品质', () => {
const level = getQualityLevel(0)
expect(level.level).toBe(1)
expect(level.name).toBe('垃圾')
expect(level.multiplier).toBe(0.5)
})
test('应该正确识别普通品质', () => {
const level = getQualityLevel(75)
expect(level.level).toBe(2)
expect(level.name).toBe('普通')
expect(level.multiplier).toBe(1.0)
})
test('应该正确识别优秀品质', () => {
const level = getQualityLevel(115)
expect(level.level).toBe(3)
expect(level.name).toBe('优秀')
expect(level.multiplier).toBe(1.3)
})
test('应该正确识别稀有品质', () => {
const level = getQualityLevel(145)
expect(level.level).toBe(4)
expect(level.name).toBe('稀有')
expect(level.multiplier).toBe(1.6)
})
test('应该正确识别史诗品质', () => {
const level = getQualityLevel(180)
expect(level.level).toBe(5)
expect(level.name).toBe('史诗')
expect(level.multiplier).toBe(2.0)
})
test('应该正确识别传说品质', () => {
const level = getQualityLevel(220)
expect(level.level).toBe(6)
expect(level.name).toBe('传说')
expect(level.multiplier).toBe(2.5)
})
test('品质边界值测试', () => {
expect(getQualityLevel(49).level).toBe(1) // 垃圾上限
expect(getQualityLevel(50).level).toBe(2) // 普通下限
expect(getQualityLevel(99).level).toBe(2) // 普通上限
expect(getQualityLevel(100).level).toBe(3) // 优秀下限
expect(getQualityLevel(250).level).toBe(6) // 传说上限
})
test('超出范围应该被限制', () => {
expect(getQualityLevel(-10).level).toBe(1) // 负数
expect(getQualityLevel(300).level).toBe(6) // 超过250
})
})
describe('calculateItemStats - 物品属性计算', () => {
test('应该正确计算武器伤害', () => {
const baseDamage = 10
const quality = 100
const qualityMultiplier = 1.3
const qualityRatio = quality / 100
const finalDamage = Math.floor(baseDamage * qualityRatio * qualityMultiplier)
expect(finalDamage).toBe(13) // 10 * 1.0 * 1.3 = 13
})
test('应该正确计算防御力', () => {
const baseDefense = 20
const quality = 150
const qualityMultiplier = 1.6
const qualityRatio = quality / 100
const finalDefense = Math.floor(baseDefense * qualityRatio * qualityMultiplier)
expect(finalDefense).toBe(48) // 20 * 1.5 * 1.6 = 48
})
test('应该正确计算物品价值', () => {
const baseValue = 100
const quality = 120
const qualityMultiplier = 1.3
const qualityRatio = quality / 100
const finalValue = Math.floor(baseValue * qualityRatio * qualityMultiplier)
expect(finalValue).toBe(156) // 100 * 1.2 * 1.3 = 156
})
test('品质为0时应该有最小值', () => {
const baseDamage = 10
const quality = 0
const qualityMultiplier = 0.5
const qualityRatio = quality / 100
const finalDamage = Math.floor(baseDamage * qualityRatio * qualityMultiplier)
expect(finalDamage).toBe(0) // 10 * 0 * 0.5 = 0
})
})
describe('generateRandomQuality - 随机品质生成', () => {
test('基础品质范围应该是50-150', () => {
const baseMin = 50
const baseMax = 150
const randomQuality = baseMin + Math.floor(Math.random() * (baseMax - baseMin + 1))
expect(randomQuality).toBeGreaterThanOrEqual(50)
expect(randomQuality).toBeLessThanOrEqual(150)
})
test('运气应该增加品质', () => {
const baseQuality = 100
const luck = 20
const luckBonus = luck * 0.5
const finalQuality = baseQuality + luckBonus
expect(finalQuality).toBe(110) // 100 + 20 * 0.5 = 110
})
test('品质应该被限制在0-250之间', () => {
const clamp = (value) => Math.min(250, Math.max(0, value))
expect(clamp(-10)).toBe(0)
expect(clamp(0)).toBe(0)
expect(clamp(250)).toBe(250)
expect(clamp(300)).toBe(250)
})
test('传说品质应该非常稀有', () => {
// 传说品质概率 < 0.02
const legendaryChance = 0.01 + 50 / 10000
expect(legendaryChance).toBeLessThan(0.02)
})
test('史诗品质应该稀有', () => {
// 史诗品质概率 < 0.08
const epicChance = 0.05 + 50 / 2000
expect(epicChance).toBeLessThan(0.08)
})
})
describe('calculateSellPrice - 出售价格计算', () => {
test('应该正确计算基础出售价格', () => {
const baseValue = 100
const sellRate = 0.5 // 50%
const sellPrice = Math.floor(baseValue * sellRate)
expect(sellPrice).toBe(50)
})
test('品质应该影响出售价格', () => {
const baseValue = 100
const quality = 120
const qualityMultiplier = 1.3
const sellRate = 0.5
const sellPrice = Math.floor(baseValue * (quality / 100) * qualityMultiplier * sellRate)
expect(sellPrice).toBe(78) // 100 * 1.2 * 1.3 * 0.5 = 78
})
test('市场倍率应该影响价格', () => {
const baseValue = 100
const quality = 100
const qualityMultiplier = 1.0
const marketRate = 1.5 // 市场繁荣
const sellPrice = Math.floor(baseValue * 1.0 * qualityMultiplier * 0.5 * marketRate)
expect(sellPrice).toBe(75) // 100 * 1.0 * 1.0 * 0.5 * 1.5 = 75
})
})
describe('calculateBuyPrice - 购买价格计算', () => {
test('应该正确计算基础购买价格', () => {
const baseValue = 100
const buyRate = 1.5 // 150%
const buyPrice = Math.floor(baseValue * buyRate)
expect(buyPrice).toBe(150)
})
test('品质应该影响购买价格', () => {
const baseValue = 100
const quality = 150
const qualityMultiplier = 1.6
const buyRate = 1.5
const buyPrice = Math.floor(baseValue * (quality / 100) * qualityMultiplier * buyRate)
expect(buyPrice).toBe(360) // 100 * 1.5 * 1.6 * 1.5 = 360
})
})
describe('物品堆叠', () => {
test('可堆叠物品应该有最大堆叠数', () => {
const stackable = true
const maxStack = 99
const canAddToStack = (currentStack, amount) => {
return currentStack + amount <= maxStack
}
expect(canAddToStack(50, 40)).toBe(true)
expect(canAddToStack(50, 49)).toBe(true)
expect(canAddToStack(50, 50)).toBe(false)
})
test('不可堆叠物品每次只能有一个', () => {
const stackable = false
const maxStack = 1
const canAddToStack = (currentStack) => {
return currentStack < maxStack
}
expect(canAddToStack(0)).toBe(true)
expect(canAddToStack(1)).toBe(false)
})
test('使用堆叠物品应该减少数量', () => {
const itemCount = 10
const useCount = 3
const remainingCount = itemCount - useCount
expect(remainingCount).toBe(7)
})
test('堆叠为0时物品应该被移除', () => {
const itemCount = 1
const useCount = 1
const shouldRemove = itemCount - useCount <= 0
expect(shouldRemove).toBe(true)
})
})
describe('装备物品', () => {
test('武器应该装备到武器槽', () => {
const itemType = 'weapon'
const equipment = {
weapon: null,
armor: null
}
const canEquip = (slot) => {
return equipment[slot] === null
}
expect(canEquip('weapon')).toBe(true)
expect(canEquip('armor')).toBe(true)
})
test('装备槽已占用时不能装备', () => {
const equipment = {
weapon: { id: 'old_sword' }
}
const canEquip = (slot) => {
return equipment[slot] === null
}
expect(canEquip('weapon')).toBe(false)
})
test('双手武器应该占用两个槽位', () => {
const item = {
id: 'two_handed_sword',
twoHanded: true
}
const slotsNeeded = item.twoHanded ? 2 : 1
expect(slotsNeeded).toBe(2)
})
})
describe('消耗品效果', () => {
test('生命药水应该恢复生命值', () => {
const currentHealth = 50
const maxHealth = 100
const healAmount = 30
const newHealth = Math.min(maxHealth, currentHealth + healAmount)
expect(newHealth).toBe(80)
})
test('生命值已满时不应该溢出', () => {
const currentHealth = 100
const maxHealth = 100
const healAmount = 30
const newHealth = Math.min(maxHealth, currentHealth + healAmount)
expect(newHealth).toBe(100)
})
test('耐力药水应该恢复耐力', () => {
const currentStamina = 20
const maxStamina = 100
const restoreAmount = 50
const newStamina = Math.min(maxStamina, currentStamina + restoreAmount)
expect(newStamina).toBe(70)
})
})
describe('边界条件', () => {
test('品质为负数时应该设为0', () => {
const quality = -10
const clampedQuality = Math.max(0, quality)
expect(clampedQuality).toBe(0)
})
test('品质超过250时应该设为250', () => {
const quality = 300
const clampedQuality = Math.min(250, quality)
expect(clampedQuality).toBe(250)
})
test('物品价值不能为负数', () => {
const baseValue = -100
const finalValue = Math.max(0, baseValue)
expect(finalValue).toBe(0)
})
test('伤害不能为负数', () => {
const baseDamage = -10
const qualityRatio = 1.0
const qualityMultiplier = 1.0
const finalDamage = Math.max(0, Math.floor(baseDamage * qualityRatio * qualityMultiplier))
expect(finalDamage).toBe(0)
})
})
})

View File

@@ -0,0 +1,403 @@
/**
* 技能系统单元测试
* 测试纯函数,不依赖框架
*/
describe('技能系统 - 纯函数测试', () => {
describe('getNextLevelExp - 下一级经验计算', () => {
test('应该正确计算下一级所需经验(默认公式)', () => {
// 默认公式: (currentLevel + 1) * 100
const getNextLevelExp = (skillId, currentLevel) => {
// 假设使用默认配置
return (currentLevel + 1) * 100
}
expect(getNextLevelExp('sword', 0)).toBe(100) // 1级需要100
expect(getNextLevelExp('sword', 1)).toBe(200) // 2级需要200
expect(getNextLevelExp('sword', 5)).toBe(600) // 6级需要600
expect(getNextLevelExp('sword', 9)).toBe(1000) // 10级需要1000
})
test('应该正确处理自定义经验公式', () => {
// 自定义公式: level * level * 50
const customExpPerLevel = (level) => level * level * 50
const getNextLevelExp = (skillId, currentLevel) => {
return customExpPerLevel(currentLevel + 1)
}
expect(getNextLevelExp('magic', 0)).toBe(50) // 1级: 1*1*50
expect(getNextLevelExp('magic', 1)).toBe(200) // 2级: 2*2*50
expect(getNextLevelExp('magic', 4)).toBe(1250) // 5级: 5*5*50
})
test('边界值测试', () => {
const getNextLevelExp = (skillId, currentLevel) => {
return (currentLevel + 1) * 100
}
expect(getNextLevelExp('test', 0)).toBe(100)
expect(getNextLevelExp('test', 99)).toBe(10000)
})
})
describe('技能升级逻辑', () => {
test('应该正确添加经验并升级', () => {
// 模拟升级逻辑
const addSkillExp = (skill, amount, expPerLevel) => {
skill.exp += amount
let leveledUp = false
while (skill.exp >= expPerLevel(skill.level + 1)) {
skill.exp -= expPerLevel(skill.level + 1)
skill.level++
leveledUp = true
}
return { leveledUp, newLevel: skill.level }
}
const skill = { level: 1, exp: 0 }
// 2级需要 200 经验
const expPerLevel = (level) => level * 100
// 添加200经验正好升级到2级
let result = addSkillExp(skill, 200, expPerLevel)
expect(result.leveledUp).toBe(true)
expect(result.newLevel).toBe(2)
expect(skill.exp).toBe(0)
// 再添加300经验升级到3级需要300
result = addSkillExp(skill, 300, expPerLevel)
expect(result.leveledUp).toBe(true)
expect(result.newLevel).toBe(3)
expect(skill.exp).toBe(0)
})
test('应该正确处理连续升级', () => {
const skill = { level: 1, exp: 0 }
const expPerLevel = (level) => level * 100
// 添加500经验应该从1级升到3级200+300
const addSkillExp = (skill, amount, expPerLevel) => {
skill.exp += amount
let leveledUp = false
while (skill.exp >= expPerLevel(skill.level + 1)) {
skill.exp -= expPerLevel(skill.level + 1)
skill.level++
leveledUp = true
}
return { leveledUp, newLevel: skill.level }
}
const result = addSkillExp(skill, 500, expPerLevel)
expect(result.leveledUp).toBe(true)
expect(result.newLevel).toBe(3)
expect(skill.exp).toBe(0)
})
test('经验不足时不应该升级', () => {
const skill = { level: 1, exp: 0 }
const expPerLevel = (level) => level * 100
const addSkillExp = (skill, amount, expPerLevel) => {
skill.exp += amount
let leveledUp = false
while (skill.exp >= expPerLevel(skill.level + 1)) {
skill.exp -= expPerLevel(skill.level + 1)
skill.level++
leveledUp = true
}
return { leveledUp, newLevel: skill.level }
}
const result = addSkillExp(skill, 50, expPerLevel)
expect(result.leveledUp).toBe(false)
expect(result.newLevel).toBe(1)
expect(skill.exp).toBe(50)
})
test('部分成功时经验应该减半', () => {
const EXP_PARTIAL_SUCCESS = 0.5
const calculatePartialExp = (amount) => {
return Math.floor(amount * EXP_PARTIAL_SUCCESS)
}
expect(calculatePartialExp(100)).toBe(50)
expect(calculatePartialExp(150)).toBe(75)
expect(calculatePartialExp(99)).toBe(49) // 向下取整
})
})
describe('技能等级上限', () => {
test('应该正确计算等级上限(玩家等级限制)', () => {
// 技能上限 = 玩家等级 * 2
const playerLevel = 5
const maxSkillLevel = playerLevel * 2
expect(maxSkillLevel).toBe(10)
const skillLevel = 10
const canLevelUp = skillLevel < maxSkillLevel
expect(canLevelUp).toBe(false)
})
test('应该正确计算等级上限(技能配置限制)', () => {
const playerLevel = 10
const skillMaxLevel = 5
// 取最小值
const effectiveMaxLevel = Math.min(playerLevel * 2, skillMaxLevel)
expect(effectiveMaxLevel).toBe(5)
})
test('达到等级上限时不再升级', () => {
const skill = { level: 5, exp: 0 }
const maxLevel = 5
const addSkillExp = (skill, amount, maxLevel) => {
if (skill.level >= maxLevel) {
return { leveledUp: false, newLevel: skill.level, capped: true }
}
skill.exp += amount
return { leveledUp: false, newLevel: skill.level, capped: false }
}
const result = addSkillExp(skill, 100, maxLevel)
expect(result.leveledUp).toBe(false)
expect(result.capped).toBe(true)
expect(result.newLevel).toBe(5)
})
})
describe('里程碑奖励', () => {
test('应该正确检测里程碑等级', () => {
const milestones = {
5: { effect: { damage: 5 } },
10: { effect: { damage: 10 } }
}
const hasMilestone = (level) => {
return !!milestones[level]
}
expect(hasMilestone(5)).toBe(true)
expect(hasMilestone(10)).toBe(true)
expect(hasMilestone(3)).toBe(false)
})
test('应该正确应用里程碑奖励', () => {
const skill = { level: 4 }
const milestones = {
5: { effect: { strength: 2 } },
10: { effect: { strength: 5 } }
}
const applyMilestone = (skill, milestones) => {
if (milestones[skill.level]) {
return milestones[skill.level].effect
}
return {}
}
// 5级时应用
skill.level = 5
const effect = applyMilestone(skill, milestones)
expect(effect).toEqual({ strength: 2 })
// 10级时应用
skill.level = 10
const effect2 = applyMilestone(skill, milestones)
expect(effect2).toEqual({ strength: 5 })
})
test('里程碑奖励应该只应用一次', () => {
const appliedMilestones = new Set()
const milestoneKey = 'sword_5'
const canApplyMilestone = (key) => {
return !appliedMilestones.has(key)
}
const applyMilestone = (key) => {
if (canApplyMilestone(key)) {
appliedMilestones.add(key)
return true
}
return false
}
expect(applyMilestone(milestoneKey)).toBe(true)
expect(applyMilestone(milestoneKey)).toBe(false)
expect(appliedMilestones.has(milestoneKey)).toBe(true)
})
})
describe('父技能系统', () => {
test('父技能等级应该等于所有子技能的最高等级', () => {
const childSkills = {
sword: { level: 5 },
axe: { level: 3 },
spear: { level: 7 }
}
const calculateParentLevel = (childSkills) => {
return Math.max(...Object.values(childSkills).map(s => s.level))
}
expect(calculateParentLevel(childSkills)).toBe(7)
})
test('子技能升级时应该更新父技能', () => {
const skills = {
sword: { level: 5 },
axe: { level: 3 },
combat: { level: 5 } // 父技能
}
const updateParentSkill = (skillId) => {
const childLevels = [skills.sword.level, skills.axe.level]
skills.combat.level = Math.max(...childLevels)
}
// sword 升到 7
skills.sword.level = 7
updateParentSkill('sword')
expect(skills.combat.level).toBe(7)
})
test('父技能应该自动初始化', () => {
const skills = {
sword: { level: 3, unlocked: true },
axe: { level: 0, unlocked: false }
}
const initParentSkill = () => {
if (!skills.combat) {
skills.combat = { level: 0, unlocked: true }
}
}
initParentSkill()
expect(skills.combat).toBeDefined()
expect(skills.combat.unlocked).toBe(true)
})
})
describe('经验倍率计算', () => {
test('应该正确计算基础经验倍率', () => {
const globalBonus = 0.2 // 20%全局加成
const milestoneBonus = 0.1 // 10%里程碑加成
const totalBonus = globalBonus + milestoneBonus
expect(totalBonus).toBeCloseTo(0.3, 1)
})
test('父技能高于子技能时应该有1.5倍加成', () => {
const parentLevel = 10
const childLevel = 5
const hasParentBonus = parentLevel > childLevel
const expMultiplier = hasParentBonus ? 1.5 : 1.0
expect(expMultiplier).toBe(1.5)
})
test('应该正确应用多个加成', () => {
const baseExp = 100
const globalBonus = 1.2 // +20%
const parentBonus = 1.5 // +50%
const milestoneBonus = 1.1 // +10%
const finalExp = baseExp * globalBonus * parentBonus * milestoneBonus
expect(finalExp).toBeCloseTo(198, 0) // 100 * 1.2 * 1.5 * 1.1 = 198
})
})
describe('技能解锁条件', () => {
test('应该正确检查物品条件', () => {
const playerInventory = ['stick', 'sword']
const requiredItem = 'stick'
const hasRequiredItem = (inventory, item) => {
return inventory.includes(item)
}
expect(hasRequiredItem(playerInventory, requiredItem)).toBe(true)
expect(hasRequiredItem(playerInventory, 'axe')).toBe(false)
})
test('应该正确检查位置条件', () => {
const playerLocation = 'camp'
const requiredLocation = 'camp'
const isInLocation = (current, required) => {
return current === required
}
expect(isInLocation(playerLocation, requiredLocation)).toBe(true)
expect(isInLocation(playerLocation, 'wild1')).toBe(false)
})
test('应该正确检查前置技能等级', () => {
const skills = {
basic: { level: 5, unlocked: true },
advanced: { level: 0, unlocked: false }
}
const checkPrerequisite = (skillId, requiredLevel) => {
const skill = skills[skillId]
return skill && skill.level >= requiredLevel
}
expect(checkPrerequisite('basic', 5)).toBe(true)
expect(checkPrerequisite('basic', 6)).toBe(false)
})
})
describe('边界条件', () => {
test('经验不能为负数', () => {
const skill = { level: 1, exp: 50 }
const addExpSafe = (skill, amount) => {
skill.exp = Math.max(0, skill.exp + amount)
return skill.exp
}
expect(addExpSafe(skill, -30)).toBe(20)
expect(addExpSafe(skill, -100)).toBe(0)
})
test('等级不能为负数', () => {
const skill = { level: 1, exp: 0 }
const levelUpSafe = (skill) => {
skill.level = Math.max(0, skill.level + 1)
return skill.level
}
expect(levelUpSafe(skill)).toBe(2)
})
test('空技能对象应该有默认值', () => {
const createSkill = () => ({
level: 0,
exp: 0,
unlocked: false
})
const skill = createSkill()
expect(skill.level).toBe(0)
expect(skill.exp).toBe(0)
expect(skill.unlocked).toBe(false)
})
})
})