import { summary } from '../src/functions/summary.js' import { readFile } from 'fs/promises'; import Life from '../src/life.js'; globalThis.json = async fileName => JSON.parse(await readFile(`data/${fileName}.json`)); globalThis.$$eventMap = new Map(); globalThis.$$event = (tag, data) => { const listener = $$eventMap.get(tag); if(listener) listener.forEach(fn=>fn(data)); } globalThis.$$on = (tag, fn) => { let listener = $$eventMap.get(tag); if(!listener) { listener = new Set(); $$eventMap.set(tag, listener); } listener.add(fn); } globalThis.$$off = (tag, fn) => { const listener = $$eventMap.get(tag); if(listener) listener.delete(fn); } class App { constructor() { this.#life = new Life(); } Steps= { TALENT: 'talent', PROPERTY: 'property', TRAJECTORY: 'trajectory', SUMMARY: 'summary', }; #step = this.Steps.SUMMARY; #life; #talentSelected = new Set(); #talentExtend = new Set(); #input; #auto; #isEnd; #propertyAllocation; #output; #exit; #interval; #style = { warn: ['\x1B[93m', '\x1B[39m'], // Bright Yellow grade1: ['\x1B[94m', '\x1B[39m'], // Bright Blue grade2: ['\x1B[95m', '\x1B[39m'], // Bright Magenta grade3: ['\x1B[93m', '\x1B[39m'], // Bright Yellow grade1b: ['\x1B[94m\x1B[7m', '\x1B[0m'], // Bright Blue BG grade2b: ['\x1B[95m\x1B[7m', '\x1B[0m'], // Bright Magenta BG grade3b: ['\x1B[93m\x1B[7m', '\x1B[0m'], // Bright Yellow BG }; #randomTalents; style(type, str) { const style = this.#style[type]; if(!style) return str; return `${style[0]}${str}${style[1]}`; } async initial() { this.output('Now Loading...'); this.#talentExtend = localStorage.talentExtend; await this.#life.initial(); this.output(`\rLoading Complete. 人生重开模拟器 这垃圾人生一秒也不想待了 键入 \x1B[4m/remake\x1B[24m 开始游戏 键入 \x1B[4m/help\x1B[24m 获取帮助`, true ); $$on('achievement', ({name})=>this.output(` ------------------------- 解锁成就【${name}】 ------------------------- `)) } io(input, output, exit) { this.#input = input; this.#output = output; this.#exit = exit; input(command=>{ const ret = this.repl(command); if(!ret) return; if(typeof ret == 'string') return this.output(ret, true); if(Array.isArray(ret)) return this.output(...ret); const { message, isRepl } = ret; return this.output(message, isRepl); }); } output(data, isRepl) { if(!this.#output) return; this.#output(data, isRepl); } exit(code) { if(this.#exit) this.#exit(code); process.exit(code); } repl(command) { command = command.split(/\s+/); switch(command.shift()) { case 'r': case 'remake': case '/remake':return this.remake(); case 's': case 'select': case '/select': return this.select(...command); case 'u': case 'unselect': case '/unselect': return this.unselect(...command); case 'n': case 'next': case '/next': case '': return this.next(true); case 'a': case 'alloc': case 'allocate': case 'attrib': case 'attribute': case '/alloc': case '/allocate': case '/attrib': case '/attribute': return this.attrib(...command); case 'rd': case 'random': case '/random': return this.random(); case 'at': case 'auto': case '/auto': return this.auto(...command); case 'x': case 'exit': case '/exit': return this.exit(0); case '?': case 'h': case 'help': case '/?': case '/h': case '/help': default: return this.help(...command); } } help(key) { switch(key) { case 'x': case 'exit': case '/exit': return `退出 x, exit, /exit 命令同等效果`; case 'r': case 'remake': case '/remake': return `重开 r, remake, /remake 命令同等效果`; case 's': case 'select': case '/select': return `选择 s, select, /select 命令同等效果 Example: /select 1 2 3 意味着选择 1 2 3 三个天赋 /select [id2] [id3] 参数解释 通常来说要指定至少一个id 虽然不指定也可以 [id2] [id3] 可以不指定`; case 'u': case 'unselect': case '/unselect': return `取消选择 u, unselect, /unselect 命令同等效果 Example: /unselect 1 2 3 意味着取消选择 1 2 3 三个天赋 参数解释 /unselect [id2] [id3] 通常来说要指定至少一个id 虽然不指定也可以 [id2] [id3] 可以不指定`; case 'a': case 'alloc': case 'allocate': case 'attrib': case 'attribute': case '/alloc': case '/allocate': case '/attrib': case '/attribute': return `分配或查看属性点 a, alloc, allocate, attrib, attribute /alloc, /allocate, /attrib, /attribute 命令同等效果 Example: /attribute /allocate STR 1 /allocate INT -3 /allocate CHR +5 效果 在属性分配时分配属性点 在人生的过程中查看当前属性点 参数解释 /allocate <[+/-]value> 表示要分配的属性标签 可选有 CHR, chr, c, C 表示颜值 INT, int, i, I 表示智力 STR, str, s, S 表示体质 MNY, mny, m, M 表示家境 必填 <[+/-]value> 表示属性的调整 其中 + 表示在当前基础上增加 - 表示在当前基础上减少 无符号表示直接设置为此值 必填`; case 'n': case 'next': case '/next': return `继续 n, next, /next 命令同等效果 效果 通常用于各步骤结束后 例如: 选择天赋后 分配属性后 每个年龄事件后 总评后 继承天赋后`; case 'at': case 'auto': case '/auto': return `自动播放 at, auto, /auto 命令同等效果 效果 用于人生的过程中 每个年龄会自动下一年 播放速度 1 秒 1 年`; case '?': case 'h': case 'help': case '/?': case '/h': case '/help': return `显示帮助 ?, h, help /?, /h, /help 命令同等效果 Example: /help /help /select 参数解释 /help [command] [command] 要详细显示帮助的命令 可以不填`; } return `Help --- 命令 说明 示例 x exit /exit 退出 /exit r remake /remake 重开 /remake s select /select 选择天赋 /select [id2] [id3] u unselect /unselect 取消选择 /unselect [id2] [id3] a alloc allocate attrib attribute /alloc /allocate /attrib /attribute 分配或查看属性点 /allocate <[+/-]value> n next /next 继续 /next at auto /auto 自动播放 /auto ? h help /? /h /help 显示帮助 /help [command]`; } auto(arg) { this.#auto = arg != 'off'; return this.next(true); } remake() { if(this.#talentExtend) { this.#life.talentExtend(this.#talentExtend) dumpLocalStorage(); this.#talentExtend = null; } this.#isEnd = false; this.#talentSelected.clear(); this.#propertyAllocation = {CHR:0,INT:0,STR:0,MNY:0,SPR:5}; this.#step = this.Steps.TALENT; this.#randomTalents = this.#life.talentRandom(); return this.list(); } select(...select) { switch(this.#step) { case this.Steps.TALENT: return this.talentSelect(...select); case this.Steps.SUMMARY: return this.talentExtend(...select); } } unselect(...select) { switch(this.#step) { case this.Steps.TALENT: return this.talentUnSelect(...select); case this.Steps.SUMMARY: return this.talentExtendCancle(...select); } } talentSelect(...select) { const warn = str => `${this.list()}\n${this.style('warn', str)}`; for(const number of select) { const s = this.#randomTalents[number]; if(!s) return warn(`${number} 为未知天赋`); if(this.#talentSelected.has(s)) continue; if(this.#talentSelected.size == 3) return warn('你只能选3个天赋。请使用 \x1B[4m/unselect\x1B[24m 取消选择你不想要的天赋'); const exclusive = this.#life.exclusive( Array.from(this.#talentSelected).map(({id})=>id), s.id ); if(exclusive != null) for(const { name, id } of this.#talentSelected) if(id == exclusive) return warn(`天赋【${s.name}】与已选择的天赋【${name}】冲突`); this.#talentSelected.add(s); } return this.list(); } talentUnSelect(...select) { for(const number of select) { const s = this.#randomTalents[number]; if(this.#talentSelected.has(s)) this.#talentSelected.delete(s); } return this.list(); } talentExtend(select) { const warn = str => `${this.list()}\n${this.style('warn', str)}`; const list = Array.from(this.#talentSelected); const s = list[select]; if(!s) return warn(`${select} 为未知天赋`); this.#talentExtend = s.id; return this.list(); } talentExtendCancle() { this.#talentExtend = null; } list() { let description, list, check; switch(this.#step) { case this.Steps.TALENT: description = '🎉 请选择(\x1B[4m/select\x1B[24m)3 个天赋'; list = this.#randomTalents; check = talent=>this.#talentSelected.has(talent); break; case this.Steps.SUMMARY: description = '🎉 你可以选(\x1B[4m/select\x1B[24m)一个天赋继承'; list = Array.from(this.#talentSelected); check = ({id})=>this.#talentExtend == id; break; } if(!list) return ''; return [description, list.map( (talent, i) => this.style( `grade${talent.grade}b`, `${check(talent)?'√':' '} ${i} ${talent.name}(${talent.description})` ) )] .flat() .join('\n'); } next(enter) { const warn = (a, b) => `${a}\n${this.style('warn', this.style('warn', b))}`; switch(this.#step) { case this.Steps.TALENT: if(this.#talentSelected.size != 3) return warn(this.list(), `请选择 3 个天赋`); this.#step = this.Steps.PROPERTY; this.#propertyAllocation.total = 20 + this.#life.getTalentAllocationAddition( Array.from(this.#talentSelected).map(({id})=>id) ); this.#propertyAllocation.TLT = Array.from(this.#talentSelected).map(({id})=>id); return this.prop(); case this.Steps.PROPERTY: const less = this.less(); if(less > 0) return warn(this.prop(), `你还有 ${less} 属性点没有分配完`); this.#step = this.Steps.TRAJECTORY; delete this.#propertyAllocation.total; this.#life.restart(this.#propertyAllocation); return this.trajectory(enter); case this.Steps.TRAJECTORY: if(!this.#isEnd) return this.trajectory(enter); this.#step = this.Steps.SUMMARY; return `${ this.summary() }\n\n${ this.list() }`; case this.Steps.SUMMARY: return this.remake(); } } trajectory(enter) { if(enter) { if(this.#interval) { clearInterval(this.#interval); this.#interval = null; this.#auto = false; } else if(this.#auto) { this.#interval = setInterval( ()=>{ const trajectory = this.next(); if(this.#isEnd && this.#interval) { clearInterval(this.#interval); this.#interval = null; } if(!this.#isEnd) return this.output(`${trajectory}\n`); return this.output(trajectory, true); } , 1000); return; } } const trajectory = this.#life.next(); const { age, content, isEnd } = trajectory; if(isEnd) this.#isEnd = true; return `${age}岁:\t${ content.map( ({type, description, grade, name, postEvent}) => { switch(type) { case 'TLT': return `天赋【${name}】发动:${description}`; case 'EVT': return description + (postEvent?`\n\t${postEvent}`:''); } } ).join('\n\t') }`; } prop() { const { CHR, INT, STR, MNY } = this.#propertyAllocation; return `🎉 属性分配 请使用 \x1B[4m/alloc\x1B[24m 分配属性 剩余点数 ${this.less()} 属性(TAG) 当前值 颜值(CHR) ${CHR} 智力(INT) ${INT} 体质(STR) ${STR} 家境(MNY) ${MNY} ` } less() { const { total, CHR, INT, STR, MNY } = this.#propertyAllocation; return total - CHR - INT - STR - MNY; } attrib(tag, value) { switch (this.#step) { case this.Steps.PROPERTY: return this.alloc(tag, value); case this.Steps.TRAJECTORY: return this.showProperty(); default: return undefined; } } showProperty() { let property = this.#life.getLastRecord(); return `当前属性 属性(TAG) 当前值 颜值(CHR) ${property.CHR} 智力(INT) ${property.INT} 体质(STR) ${property.STR} 家境(MNY) ${property.MNY} 快乐(SPR) ${property.SPR}` } alloc(tag, value) { const warn = str => `${this.prop()}\n${this.style('warn', str)}` if(!value) return warn('⚠ 分配的数值没有给定'); const isSet = !(value[0] == '-'|| value[0] == '+'); value = Number(value); if(isNaN(value)) return warn('⚠ 分配的数值不正确'); switch(tag) { case 'c': case 'chr': case 'C': tag = 'CHR'; break; case 'i': case 'int': case 'I': tag = 'INT'; break; case 's': case 'S': case 'str': tag = 'STR'; break; case 'm': case 'M': case 'mny': tag = 'MNY'; break; } switch(tag) { case 'CHR': case 'INT': case 'STR': case 'MNY': if(isSet) value = value - this.#propertyAllocation[tag]; const tempLess = this.less() - value; const tempSet = this.#propertyAllocation[tag] + value; if(tempLess<0) return warn('⚠ 你没有更多的点数可以分配了'); if( tempLess>this.#propertyAllocation.total || tempSet < 0 ) return warn('⚠ 不能分配负数属性'); if(tempSet>10) return warn('⚠ 单项属性最高分配10点'); this.#propertyAllocation[tag] += value; return this.prop(); default: return warn('⚠ 未知的tag'); } } random() { let t = this.#propertyAllocation.total; const arr = [10, 10, 10, 10]; while(t>0) { const sub = Math.round(Math.random() * (Math.min(t, 10) - 1)) + 1; while(true) { const select = Math.floor(Math.random() * 4) % 4; if(arr[select] - sub <0) continue; arr[select] -= sub; t -= sub; break; } } this.#propertyAllocation.CHR = 10 - arr[0]; this.#propertyAllocation.INT = 10 - arr[1]; this.#propertyAllocation.STR = 10 - arr[2]; this.#propertyAllocation.MNY = 10 - arr[3]; return this.prop(); } summary() { const summaryData = this.#life.getSummary(); const format = (name, type) => { const value = summaryData[type]; const { judge, grade } = summary(type, value); return this.style(`grade${grade}b`, `${name}:${value} ${judge}`); } return [ '🎉 总评', format('颜值', 'CHR'), format('智力', 'INT'), format('体质', 'STR'), format('家境', 'MNY'), format('快乐', 'SPR'), format('享年', 'AGE'), format('总评', 'SUM'), ].join('\n'); } } export default App;