From caf08695d15ffaa9c3bc9dbd361578fd1a96c765 Mon Sep 17 00:00:00 2001 From: Vick Scarlet Date: Sun, 5 Sep 2021 22:03:44 +0800 Subject: [PATCH] add repl version --- .gitignore | 4 +- .vscode/launch.json | 11 +- repl/app.js | 457 ++++++++++++++++++++++++++++++++++++++++++++ repl/index.js | 30 +++ 4 files changed, 500 insertions(+), 2 deletions(-) create mode 100644 repl/app.js create mode 100644 repl/index.js diff --git a/.gitignore b/.gitignore index 263ed35..5089eae 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,6 @@ dist utils/xlsxTransform-* -/.idea \ No newline at end of file +/.idea + +__localStorage.json \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 1db31f9..e3fb93e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,6 +22,15 @@ "skipFiles": [ "/**" ] - } + }, + { + "name": "Attach by Process ID", + "processId": "${command:PickProcess}", + "request": "attach", + "skipFiles": [ + "/**" + ], + "type": "node" + }, ] } \ No newline at end of file diff --git a/repl/app.js b/repl/app.js new file mode 100644 index 0000000..5fff41c --- /dev/null +++ b/repl/app.js @@ -0,0 +1,457 @@ +import { max, sum } from '../src/functions/util.js'; +import { summary } from '../src/functions/summary.js' +import { readFile } from 'fs/promises'; +import Life from '../src/life.js'; + +global.json = async fileName => JSON.parse(await readFile(`data/${fileName}.json`)); + +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[104m', '\x1B[49m'], // Bright Blue BG + grade2b: ['\x1B[105m', '\x1B[49m'], // Bright Magenta BG + grade3b: ['\x1B[103m', '\x1B[49m'], // 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 = global.localStorage.talentExtend; + await this.#life.initial(); + this.output('\rLoading Complete.\ntype \x1B[4m/remake\x1B[24m to start', true); + } + + 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': return this.next(true); + + case 'a': + case 'alloc': + case 'allocation': + case '/alloc': + case '/allocation': return this.alloc(...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) { + return `Help --- + x + exit + /exit exit + + r + remake + /remake remake + + s + select + /select select + + u + unselect + /unselect unselect + + n + next + /next next + + auto + /auto auto play + + ? + h + help + /? + /h + /help show this message`; + } + + auto(arg) { + this.#auto = arg != 'off'; + return this.next(true); + } + + remake() { + if(this.#talentExtend) { + this.#life.talentExtend(this.#talentExtend) + global.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个天赋'); + + 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 = '🎉 请选择3个天赋'; + list = this.#randomTalents; + check = talent=>this.#talentSelected.has(talent); + break; + case this.Steps.SUMMARY: + description = '🎉 你可以选一个天赋继承'; + 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); + if(this.#interval) clearInterval(this.#interval); + 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.#auto = false; + } else if(this.#auto) { + this.#interval = setInterval( + ()=>{ + const trajectory = this.next(); + if(this.#isEnd && this.#interval) clearInterval(this.#interval); + 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 `Property Allocation [] +🎉 剩余点数 ${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; + } + + 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 records = this.#life.getRecord(); + const s = (type, func)=>{ + const value = func(records.map(({[type]:v})=>v)); + const { judge, grade } = summary(type, value); + return { judge, grade, value }; + }; + + const style = (name, grade, judge, value) => this.style(`grade${grade}b`, `${name}:${value} ${judge}`); + const judge = (name, type, func) => { + const { judge, grade, value } = s(type, func); + return style(name, grade, judge, value ); + } + + return [ + '🎉 总评', + judge('颜值', 'CHR', max), + judge('智力', 'INT', max), + judge('体质', 'STR', max), + judge('家境', 'MNY', max), + judge('快乐', 'SPR', max), + judge('享年', 'AGE', max), + (()=>{ + const m = type=>max(records.map(({[type]: value})=>value)); + const value = Math.floor(sum(m('CHR'), m('INT'), m('STR'), m('MNY'), m('SPR'))*2 + m('AGE')/2); + const { judge, grade } = summary('SUM', value); + return style('总评', grade, judge, value ); + })(), + ].join('\n'); + } +} + +export default App; \ No newline at end of file diff --git a/repl/index.js b/repl/index.js new file mode 100644 index 0000000..f13b72f --- /dev/null +++ b/repl/index.js @@ -0,0 +1,30 @@ +import App from './app.js'; +import { readFile, writeFile } from 'fs/promises'; + +async function main() { + + try { + global.localStorage = JSON.parse(await readFile('__localStorage.json')); + } catch (e) { + global.localStorage = {}; + } + + global.dumpLocalStorage = async ()=>await writeFile('__localStorage.json', JSON.stringify( global.localStorage)) + + const app = new App(); + app.io( + repl => process.stdin.on('data', data=>repl(data.toString().trim())), + (data, isRepl) => process.stdout.write(`${data}${isRepl?'\n>':''}`), + code=>process.exit(code) + ) + await app.initial(); +} + +main(); + +// process.stdin.setRawMode(true); + +// process.openStdin().on('keypress', function (chunk, key) { +// process.stdout.write('Get Chunk: ' + chunk + '\n'); +// if (key && key.ctrl && key.name == 'c') process.exit(); +// }); \ No newline at end of file