import { summary } from './functions/summary.js'; import { getRate, getGrade } from './functions/addition.js'; import Life from './life.js'; class App{ constructor(){ this.#life = new Life(); } #life; #pages; #currentPage; #talentSelected = new Set(); #totalMax=20; #isEnd = false; #selectedExtendTalent = null; #hintTimeout; #specialthanks; #autoTrajectory; async initial() { this.initPages(); this.switch('loading'); const [,specialthanks] = await Promise.all([ this.#life.initial(), json('specialthanks') ]); this.#specialthanks = specialthanks; this.switch('index'); globalThis.onerror = (event, source, lineno, colno, error) => { this.hint(`[ERROR] at (${source}:${lineno}:${colno})\n\n${error?.stack||error||'unknow Error'}`, 'error'); } const keyDownCallback = (keyboardEvent) => { if (keyboardEvent.which === 13 || keyboardEvent.keyCode === 13) { const pressEnterFunc = this.#pages[this.#currentPage]?.pressEnter; pressEnterFunc && typeof pressEnterFunc === 'function' && pressEnterFunc(); } } globalThis.removeEventListener('keydown', keyDownCallback); globalThis.addEventListener('keydown', keyDownCallback); } initPages() { // Loading const loadingPage = $(`
人生重开模拟器
加载中...
`); // Index const indexPage = $(`
人生重开模拟器
这垃圾人生一秒也不想呆了
`); // Init theme this.setTheme(localStorage.getItem('theme')) indexPage .find('#restart') .click(()=>this.switch('talent')); indexPage .find('#achievement') .click(()=>this.switch('achievement')); indexPage .find('#save') .click(()=>{ const data = {}; Object .keys(localStorage) .filter(v=>v.substr(0,4)!='goog') .forEach(key=>data[key] = localStorage[key]); let blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); const slice = blob.slice || blob.webkitSlice || blob.mozSlice; blob = slice.call(blob, 0, blob.size, 'application/octet-stream'); const a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); a.href = URL.createObjectURL(blob); a.download = `Remake_save_${new Date().toISOString().replace(':','.')}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }); indexPage .find('#load') .click(()=>{ const file = $(``) file.appendTo('body'); file.click(); file.on('change', (e)=>{ this.switch('loading'); const file = e.target.files[0]; if(!file) return; const reader = new FileReader(); reader.onload = () => { const data = JSON.parse(reader.result); for(const key in data) { localStorage[key] = data[key]; } this.switch('index'); this.setTheme(localStorage.getItem('theme')) if(localStorage.getItem('theme') == 'light') { indexPage.find('#themeToggleBtn').text('黑') } else{ indexPage.find('#themeToggleBtn').text('白') } this.hint('加载存档成功', 'success'); } reader.readAsText(file); }); }); if(localStorage.getItem('theme') == 'light') { indexPage.find('#themeToggleBtn').text('黑') } else{ indexPage.find('#themeToggleBtn').text('白') } indexPage .find("#themeToggleBtn") .click(() => { if(localStorage.getItem('theme') == 'light') { localStorage.setItem('theme', 'dark'); indexPage.find('#themeToggleBtn').text('白') } else { localStorage.setItem('theme', 'light'); indexPage.find('#themeToggleBtn').text('黑') } this.setTheme(localStorage.getItem('theme')) }); indexPage .find('#specialthanks') .click(()=>this.switch('specialthanks')); const specialThanksPage = $(`
`); specialThanksPage .find('#specialthanks') .click(()=>this.switch('index')); const achievementPage = $(`
统计 成就 `) achievementPage .find('#specialthanks') .click(()=>this.switch('index')); achievementPage .find('#rank') .click(()=>this.hint('别卷了,没有排行榜')); // Talent const talentPage = $(`
天赋抽卡
`); const createTalent = ({ grade, name, description }) => { return $(`
  • ${name}(${description})
  • `) }; talentPage .find('#random') .click(()=>{ talentPage.find('#random').hide(); const ul = talentPage.find('#talents'); this.#life.talentRandom() .forEach(talent=>{ const li = createTalent(talent); ul.append(li); li.click(()=>{ if(li.hasClass('selected')) { li.removeClass('selected') this.#talentSelected.delete(talent); if(this.#talentSelected.size<3) { talentPage.find('#next').text('请选择3个') } } else { if(this.#talentSelected.size==3) { this.hint('只能选3个天赋'); return; } const exclusive = this.#life.exclusive( Array.from(this.#talentSelected).map(({id})=>id), talent.id ); if(exclusive != null) { for(const { name, id } of this.#talentSelected) { if(id == exclusive) { this.hint(`与已选择的天赋【${name}】冲突`); return; } } return; } li.addClass('selected'); this.#talentSelected.add(talent); if(this.#talentSelected.size==3) { talentPage.find('#next').text('开始新人生') } } }); }); talentPage.find('#next').show() }); talentPage .find('#next') .click(()=>{ if(this.#talentSelected.size!=3) { this.hint('请选择3个天赋'); return; } talentPage.find('#next').hide() this.#totalMax = 20 + this.#life.getTalentAllocationAddition(Array.from(this.#talentSelected).map(({id})=>id)); this.switch('property'); }) // Property // hint of extension tobermory.es6-string-html const propertyPage = $(/*html*/`
    调整初始属性
    可用属性点:0
    `); propertyPage.mounted = ()=>{ propertyPage .find('#talentSelectedView').append( `
  • 已选天赋
  • ` + Array.from(this.#talentSelected) .map(({name,description})=>`
  • ${name}(${description})
  • `) .join('') ) } const groups = {}; const total = ()=>{ let t = 0; for(const type in groups) t += groups[type].get(); return t; } const freshTotal = ()=>{ propertyPage.find('#total').text(`可用属性点:${this.#totalMax - total()}`); } const getBtnGroups = (name, min, max)=>{ const group = $(`
  • ${name}      
  • `); const btnSub = $(``); const inputBox = $(``); const btnAdd = $(``); group.append(btnSub); group.append(inputBox); group.append(btnAdd); const limit = v=>{ v = Number(v)||0; v = Math.round(v); return v < min ? min : ( v > max ? max : v ) } const get = ()=>Number(inputBox.val()); const set = v=>{ inputBox.val(limit(v)); freshTotal(); } btnAdd.click(()=>{ if(total() >= this.#totalMax) { this.hint('没有可分配的点数了'); return; } set(get()+1); }); btnSub.click(()=>set(get()-1)); inputBox.on('input', ()=>{ const t = total(); let val = get(); if(t > this.#totalMax) { val -= t - this.#totalMax; } val = limit(val); if(val != inputBox.val()) { set(val); } freshTotal(); }); return {group, get, set}; } groups.CHR = getBtnGroups("颜值", 0, 10); // 颜值 charm CHR groups.INT = getBtnGroups("智力", 0, 10); // 智力 intelligence INT groups.STR = getBtnGroups("体质", 0, 10); // 体质 strength STR groups.MNY = getBtnGroups("家境", 0, 10); // 家境 money MNY const ul = propertyPage.find('#propertyAllocation'); for(const type in groups) { ul.append(groups[type].group); } propertyPage .find('#random') .click(()=>{ let t = this.#totalMax; 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; } } groups.CHR.set(10 - arr[0]); groups.INT.set(10 - arr[1]); groups.STR.set(10 - arr[2]); groups.MNY.set(10 - arr[3]); }); propertyPage .find('#start') .click(()=>{ if(total() < this.#totalMax) { this.hint(`你还有${this.#totalMax-total()}属性点没有分配完`); return; } else if (total() > this.#totalMax) { this.hint(`你多使用了${total() - this.#totalMax}属性点`); return; } const contents = this.#life.restart({ CHR: groups.CHR.get(), INT: groups.INT.get(), STR: groups.STR.get(), MNY: groups.MNY.get(), SPR: 5, TLT: Array.from(this.#talentSelected).map(({id})=>id), }); this.switch('trajectory'); this.#pages.trajectory.born(contents); // $(document).keydown(function(event){ // if(event.which == 32 || event.which == 13){ // $('#lifeTrajectory').click(); // } // }) }); // Trajectory const trajectoryPage = $(`
    `); trajectoryPage .find('#lifeTrajectory') .click(()=>{ if(this.#isEnd) return; const trajectory = this.#life.next(); const { age, content, isEnd } = trajectory; const li = $(`
  • ${age}岁:${ content.map( ({type, description, grade, name, postEvent}) => { switch(type) { case 'TLT': return `天赋【${name}】发动:${description}`; case 'EVT': return description + (postEvent?`
    ${postEvent}`:''); } } ).join('
    ') }
  • `); li.appendTo('#lifeTrajectory'); $("#lifeTrajectory").scrollTop($("#lifeTrajectory")[0].scrollHeight); if(isEnd) { $(document).unbind("keydown"); this.#isEnd = true; trajectoryPage.find('#summary').show(); trajectoryPage.find('#auto').hide(); trajectoryPage.find('#auto2x').hide(); // trajectoryPage.find('#domToImage').show(); } const property = this.#life.getLastRecord(); $("#lifeProperty").html(`
  • 颜值${property.CHR}
  • 智力${property.INT}
  • 体质${property.STR}
  • 家境${property.MNY}
  • 快乐${property.SPR}
  • `); }); // html2canvas trajectoryPage .find('#domToImage') .click(()=>{ $("#lifeTrajectory").addClass("deleteFixed"); const ua = navigator.userAgent.toLowerCase(); domtoimage.toJpeg(document.getElementById('lifeTrajectory')) .then(function (dataUrl) { let link = document.createElement('a'); link.download = '我的人生回放.jpeg'; link.href = dataUrl; link.click(); $("#lifeTrajectory").removeClass("deleteFixed"); // 微信内置浏览器,显示图片,需要用户单独保存 if(ua.match(/MicroMessenger/i)=="micromessenger") { $('#endImage').attr('src', dataUrl); } }); }) .hide(); trajectoryPage .find('#summary') .click(()=>{ clearInterval(this.#autoTrajectory); this.#autoTrajectory = null; this.switch('summary'); }); const auto = tick=>{ if(this.#autoTrajectory) { clearInterval(this.#autoTrajectory); this.#autoTrajectory = null; } else { if(!this.isEnd) trajectoryPage .find('#lifeTrajectory') .click(); this.#autoTrajectory = setInterval(()=>{ if(this.isEnd) { clearInterval(this.#autoTrajectory); this.#autoTrajectory = null; } else { trajectoryPage .find('#lifeTrajectory') .click(); } }, tick); } }; trajectoryPage .find('#auto') .click(()=>auto(1000)); trajectoryPage .find('#auto2x') .click(()=>auto(500)); // Summary const summaryPage = $(`
    人生总结
    天赋,你可以选一个,下辈子还能抽到
    `); summaryPage .find('#again') .click(()=>{ this.times ++; this.#life.talentExtend(this.#selectedExtendTalent); this.#selectedExtendTalent = null; this.#talentSelected.clear(); this.#totalMax = 20; this.#isEnd = false; this.switch('index'); }); this.#pages = { loading: { page: loadingPage, clear: ()=>{ this.#currentPage = 'loading'; }, }, index: { page: indexPage, btnAchievement: indexPage.find('#achievement'), btnRestart: indexPage.find('#restart'), hint: indexPage.find('.hint'), pressEnter: ()=>{ this.#pages.index.btnRestart.click(); }, clear: ()=>{ this.#currentPage = 'index'; indexPage.find('.hint').hide(); const times = this.times; const achievement = indexPage.find('#achievement'); const discord = indexPage.find('#discord'); const specialthanks = indexPage.find('#specialthanks'); if(times > 0) { achievement.show(); discord.show(); specialthanks.show(); return; } achievement.hide(); discord.hide(); specialthanks.hide(); }, }, specialthanks: { page: specialThanksPage, clear: () => { const groups = [ specialThanksPage.find('#spthx > ul.g1'), specialThanksPage.find('#spthx > ul.g2'), ]; groups.forEach(g=>g.empty()); this.#specialthanks .sort(()=>0.5-Math.random()) .forEach(({group, name, comment, color})=>groups[--group].append(`
  • ${name} ${comment||''}
  • `)) } }, achievement: { page: achievementPage, clear: () => { const total = achievementPage.find("ul#total"); const achievements = achievementPage.find("ul#achievements"); total.empty(); achievements.empty(); const formatRate = (type, value) => { const rate = getRate(type, value); let color = Object.keys(rate)[0]; switch(parseInt(color)) { case 0: color = '白色'; break; case 1: color = '蓝色'; break; case 2: color = '紫色'; break; case 3: color = '橙色'; break; default: break; } let r = Object.values(rate)[0]; switch(parseInt(r)) { case 1: r = '不变'; break; case 2: r = '翻倍'; break; case 3: r = '三倍'; break; case 4: r = '四倍'; break; case 5: r = '五倍'; break; case 6: r = '六倍'; break; default: break; } return `抽到${color}概率${r}`; } const { times, achievement, talentRate, eventRate } = this.#life.getTotal(); total.append(`
  • 已重开${times}次${formatRate('times', times)}
  • 成就达成${achievement}个${formatRate('achievement', achievement)}
  • 事件收集率${Math.floor(eventRate * 100)}%
  • 天赋收集率${Math.floor(talentRate * 100)}%
  • `); const achievementsData = this.#life.getAchievements(); achievementsData.forEach(({ name, description, hide, grade, isAchieved })=>{ if(hide && !isAchieved) name = description = '???'; achievements.append( `
  • ${name}${description}
  • ` ); }) } }, talent: { page: talentPage, talentList: talentPage.find('#talents'), btnRandom: talentPage.find('#random'), btnNext: talentPage.find('#next'), pressEnter: ()=>{ const talentList = this.#pages.talent.talentList; const btnRandom = this.#pages.talent.btnRandom; const btnNext = this.#pages.talent.btnNext; if (talentList.children().length) { btnNext.click(); } else { btnRandom.click(); } }, clear: ()=>{ this.#currentPage = 'talent'; talentPage.find('ul.selectlist').empty(); talentPage.find('#random').show(); this.#totalMax = 20; }, }, property: { page: propertyPage, btnStart: propertyPage.find('#start'), pressEnter: ()=>{ this.#pages.property.btnStart.click(); }, clear: ()=>{ this.#currentPage = 'property'; freshTotal(); propertyPage .find('#talentSelectedView') .empty(); }, }, trajectory: { page: trajectoryPage, lifeTrajectory: trajectoryPage.find('#lifeTrajectory'), pressEnter: ()=>{ this.#pages.trajectory.lifeTrajectory.click(); }, clear: ()=>{ this.#currentPage = 'trajectory'; trajectoryPage.find('#lifeTrajectory').empty(); trajectoryPage.find('#summary').hide(); trajectoryPage.find('#auto').show(); trajectoryPage.find('#auto2x').show(); this.#isEnd = false; }, born: contents => { if(contents.length > 0) $('#lifeTrajectory') .append(`
  • 初始:${ contents.map( ({source, target}) => `天赋【${source.name}】发动:替换为天赋【${target.name}】` ).join('
    ') }
  • `); trajectoryPage.find('#lifeTrajectory').trigger("click"); } }, summary: { page: summaryPage, clear: ()=>{ this.#currentPage = 'summary'; const judge = summaryPage.find('#judge'); const talents = summaryPage.find('#talents'); judge.empty(); talents.empty(); const lastExtendTalent = this.#life.getLastExtendTalent(); Array .from(this.#talentSelected) .sort(( {id:a, grade:ag}, {id:b, grade:bg}, )=>{ if(a == lastExtendTalent) return -1; if(b == lastExtendTalent) return 1; return bg - ag; }) .forEach((talent, i)=>{ const li = createTalent(talent); talents.append(li); li.click(()=>{ if(li.hasClass('selected')) { this.#selectedExtendTalent = null; li.removeClass('selected'); } else if(this.#selectedExtendTalent != null) { this.hint('只能继承一个天赋'); return; } else { this.#selectedExtendTalent = talent.id; li.addClass('selected'); } }); if(!i) li.click(); }); const summaryData = this.#life.getSummary(); const format = (discription, type)=>{ const value = summaryData[type]; const { judge, grade } = summary(type, value); return `
  • ${discription}:${value} ${judge}
  • `; }; judge.append(` ${format('颜值', 'CHR')} ${format('智力', 'INT')} ${format('体质', 'STR')} ${format('家境', 'MNY')} ${format('快乐', 'SPR')} ${format('享年', 'AGE')} ${format('总评', 'SUM')} `); } }, } $$on('achievement', ({name})=>{ this.hint(`解锁成就【${name}】`, 'success'); }) } switch(page) { const p = this.#pages[page]; if(!p) return; $('#main').detach(); p.clear(); p.page.appendTo('body'); if(typeof p.page.mounted === 'function'){ p.page.mounted() } } hint(message, type='info') { if(this.#hintTimeout) { clearTimeout(this.#hintTimeout); this.#hintTimeout = null; } hideBanners(); requestAnimationFrame(() => { const banner = $(`.banner.${type}`); banner.addClass('visible'); banner.find('.banner-message').text(message); if(type != 'error') { this.#hintTimeout = setTimeout(hideBanners, 3000); } }); } setTheme(theme) { const themeLink = $(document).find('#themeLink'); if(theme == 'light') { themeLink.attr('href', 'light.css'); } else { themeLink.attr('href', 'dark.css'); } } get times() {return this.#life?.times || 0;} set times(v) { if(this.#life) this.#life.times = v }; } export default App;