add achievement

This commit is contained in:
Vick Scarlet
2021-09-09 22:50:03 +08:00
parent d72b6c553f
commit e06139d5c0
10 changed files with 1537 additions and 48 deletions

1334
data/achievement.json Normal file

File diff suppressed because it is too large Load Diff

BIN
data/achievement.xlsx Normal file

Binary file not shown.

View File

@@ -1,9 +1,26 @@
import { max, sum } from '../src/functions/util.js';
import { summary } from '../src/functions/summary.js' import { summary } from '../src/functions/summary.js'
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import Life from '../src/life.js'; import Life from '../src/life.js';
global.json = async fileName => JSON.parse(await readFile(`data/${fileName}.json`)); 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 { class App {
constructor() { constructor() {
@@ -47,7 +64,7 @@ class App {
async initial() { async initial() {
this.output('Now Loading...'); this.output('Now Loading...');
this.#talentExtend = global.localStorage.talentExtend; this.#talentExtend = localStorage.talentExtend;
await this.#life.initial(); await this.#life.initial();
this.output(`\rLoading Complete. this.output(`\rLoading Complete.
人生重开模拟器 人生重开模拟器
@@ -55,6 +72,11 @@ class App {
\n🎉键入 \x1B[4m/remake\x1B[24m 开始游戏`, \n🎉键入 \x1B[4m/remake\x1B[24m 开始游戏`,
true true
); );
$$on('achievement', ({name})=>this.output(`
-------------------------
解锁成就【${name}
-------------------------
`))
} }
io(input, output, exit) { io(input, output, exit) {
@@ -289,7 +311,7 @@ class App {
remake() { remake() {
if(this.#talentExtend) { if(this.#talentExtend) {
this.#life.talentExtend(this.#talentExtend) this.#life.talentExtend(this.#talentExtend)
global.dumpLocalStorage(); dumpLocalStorage();
this.#talentExtend = null; this.#talentExtend = null;
} }
@@ -549,34 +571,22 @@ class App {
} }
summary() { summary() {
const summaryData = this.#life.getSummary();
const records = this.#life.getRecord(); const format = (name, type) => {
const s = (type, func)=>{ const value = summaryData[type];
const value = func(records.map(({[type]:v})=>v));
const { judge, grade } = summary(type, value); const { judge, grade } = summary(type, value);
return { judge, grade, value }; return this.style(`grade${grade}b`, `${name}${value} ${judge}`);
};
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 [ return [
'🎉 总评', '🎉 总评',
judge('颜值', 'CHR', max), format('颜值', 'CHR'),
judge('智力', 'INT', max), format('智力', 'INT'),
judge('体质', 'STR', max), format('体质', 'STR'),
judge('家境', 'MNY', max), format('家境', 'MNY'),
judge('快乐', 'SPR', max), format('快乐', 'SPR'),
judge('享年', 'AGE', max), format('享年', 'AGE'),
(()=>{ format('总评', 'SUM'),
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'); ].join('\n');
} }
} }

View File

@@ -4,12 +4,14 @@ import { readFile, writeFile } from 'fs/promises';
async function main() { async function main() {
try { try {
global.localStorage = JSON.parse(await readFile('__localStorage.json')); globalThis.localStorage = JSON.parse(await readFile('__localStorage.json'));
} catch (e) { } catch (e) {
global.localStorage = {}; globalThis.localStorage = {};
} }
localStorage.getItem = key => localStorage[key]===void 0? null: localStorage[key];
localStorage.setItem = (key, value) => (localStorage[key] = value);
global.dumpLocalStorage = async ()=>await writeFile('__localStorage.json', JSON.stringify( global.localStorage)) globalThis.dumpLocalStorage = async ()=>await writeFile('__localStorage.json', JSON.stringify( global.localStorage))
const app = new App(); const app = new App();
app.io( app.io(

63
src/achievement.js Normal file
View File

@@ -0,0 +1,63 @@
import { clone } from './functions/util.js';
import { checkCondition } from './functions/condition.js';
class Achievement {
constructor() {}
// 时机
Opportunity = {
START: "START", // 分配完成点数,点击开始新人生后
TRAJECTORY: "TRAJECTORY", // 每一年的人生经历中
SUMMARY: "SUMMARY", // 人生结束,点击人生总结后
END: "END", // 游戏完成,点击重开 重开次数在这之后才会+1
};
#achievements;
initial({achievements}) {
this.#achievements = achievements;
}
list(property) {
return Object
.values(this.#achievements)
.map(({
id, name, opportunity,
description, hide, grade,
})=>({
id, name, opportunity,
description, hide, grade,
isAchieved: this.isAchieved(id, property),
}));
}
get(achievementId) {
const achievement = this.#achievements[achievementId];
if(!achievement) throw new Error(`[ERROR] No Achievement[${achievementId}]`);
return clone(achievement);
}
check(achievementId, property) {
const { condition } = this.get(achievementId);
return checkCondition(property, condition);
}
isAchieved(achievementId, property) {
for(const [achieved] of (property.get(property.TYPES.ACHV)||[]))
if(achieved == achievementId) return true;
return false;
}
achieve(opportunity, property) {
this.list(property)
.filter(({isAchieved})=>!isAchieved)
.filter(({opportunity: o})=>o==opportunity)
.filter(({id})=>this.check(id, property))
.forEach(({id})=>{
property.achieve(property.TYPES.ACHV, id)
$$event('achievement', this.get(id))
});
}
}
export default Achievement;

View File

@@ -1,4 +1,3 @@
import { max, sum } from './functions/util.js';
import { summary } from './functions/summary.js' import { summary } from './functions/summary.js'
import Life from './life.js' import Life from './life.js'
@@ -26,7 +25,7 @@ class App{
]); ]);
this.#specialthanks = specialthanks; this.#specialthanks = specialthanks;
this.switch('index'); this.switch('index');
window.onerror = (event, source, lineno, colno, error) => { globalThis.onerror = (event, source, lineno, colno, error) => {
this.hint(`[ERROR] at (${source}:${lineno}:${colno})\n\n${error?.stack||error||'unknow Error'}`, 'error'); this.hint(`[ERROR] at (${source}:${lineno}:${colno})\n\n${error?.stack||error||'unknow Error'}`, 'error');
} }
const keyDownCallback = (keyboardEvent) => { const keyDownCallback = (keyboardEvent) => {
@@ -35,8 +34,8 @@ class App{
pressEnterFunc && typeof pressEnterFunc === 'function' && pressEnterFunc(); pressEnterFunc && typeof pressEnterFunc === 'function' && pressEnterFunc();
} }
} }
window.removeEventListener('keydown', keyDownCallback); globalThis.removeEventListener('keydown', keyDownCallback);
window.addEventListener('keydown', keyDownCallback); globalThis.addEventListener('keydown', keyDownCallback);
} }
initPages() { initPages() {
@@ -109,8 +108,8 @@ class App{
<ul class="g1"></ul> <ul class="g1"></ul>
<ul class="g2"></ul> <ul class="g2"></ul>
</div> </div>
<button class="sponsor" onclick="window.open('https://afdian.net/@LifeRestart')" style="background: linear-gradient(90deg,#946ce6,#7e5fd9); left:auto; right:50%; transform: translate(-2rem,-50%);">打赏策划(爱发电)</button> <button class="sponsor" onclick="globalThis.open('https://afdian.net/@LifeRestart')" style="background: linear-gradient(90deg,#946ce6,#7e5fd9); left:auto; right:50%; transform: translate(-2rem,-50%);">打赏策划(爱发电)</button>
<button class="sponsor" onclick="window.open('https://dun.mianbaoduo.com/@vickscarlet')" style="background-color:#c69; left:50%; right:auto; transform: translate(2rem,-50%);">打赏程序(顿顿饭)</button> <button class="sponsor" onclick="globalThis.open('https://dun.mianbaoduo.com/@vickscarlet')" style="background-color:#c69; left:50%; right:auto; transform: translate(2rem,-50%);">打赏程序(顿顿饭)</button>
</div> </div>
`); `);
@@ -374,10 +373,10 @@ class App{
const property = this.#life.getLastRecord(); const property = this.#life.getLastRecord();
$("#lifeProperty").html(` $("#lifeProperty").html(`
<li><span>颜值</span><span>${property.CHR}</span></li> <li><span>颜值</span><span>${property.CHR}</span></li>
<li><span>智力</span><span>${property.INT}</span</li> <li><span>智力</span><span>${property.INT}</span></li>
<li><span>体质</span><span>${property.STR}</span</li> <li><span>体质</span><span>${property.STR}</span></li>
<li><span>家境</span><span>${property.MNY}</span</li> <li><span>家境</span><span>${property.MNY}</span></li>
<li><span>快乐</span><span>${property.SPR}</span</li> <li><span>快乐</span><span>${property.SPR}</span></li>
`); `);
} }
}); });
@@ -587,6 +586,10 @@ class App{
} }
}, },
} }
$$on('achievement', ({name})=>{
this.hint(`解锁成就【${name}`, 'success');
})
} }
switch(page) { switch(page) {

View File

@@ -1,10 +1,27 @@
import App from '../src/app.js'; import App from '../src/app.js';
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);
}
window.json = async fileName => await (await fetch(`../data/${fileName}.json`)).json(); globalThis.json = async fileName => await (await fetch(`../data/${fileName}.json`)).json();
// Pssst, I've created a github package - https://github.com/brookesb91/dismissible // Pssst, I've created a github package - https://github.com/brookesb91/dismissible
window.hideBanners = (e) => { globalThis.hideBanners = (e) => {
document document
.querySelectorAll(".banner.visible") .querySelectorAll(".banner.visible")
.forEach((b) => b.classList.remove("visible")); .forEach((b) => b.classList.remove("visible"));

View File

@@ -1,28 +1,33 @@
import Property from './property.js'; import Property from './property.js';
import Event from './event.js'; import Event from './event.js';
import Talent from './talent.js'; import Talent from './talent.js';
import Achievement from './achievement.js';
class Life { class Life {
constructor() { constructor() {
this.#property = new Property(); this.#property = new Property();
this.#event = new Event(); this.#event = new Event();
this.#talent = new Talent(); this.#talent = new Talent();
this.#achievement = new Achievement();
} }
#property; #property;
#event; #event;
#talent; #talent;
#achievement;
#triggerTalents; #triggerTalents;
async initial() { async initial() {
const [age, talents, events] = await Promise.all([ const [age, talents, events, achievements] = await Promise.all([
json('age'), json('age'),
json('talents'), json('talents'),
json('events'), json('events'),
json('achievement'),
]) ])
this.#property.initial({age}); this.#property.initial({age});
this.#talent.initial({talents}); this.#talent.initial({talents});
this.#event.initial({events}); this.#event.initial({events});
this.#achievement.initial({achievements});
} }
restart(allocation) { restart(allocation) {
@@ -30,6 +35,10 @@ class Life {
this.#property.restart(allocation); this.#property.restart(allocation);
this.doTalent(); this.doTalent();
this.#property.restartLastStep(); this.#property.restartLastStep();
this.#achievement.achieve(
this.#achievement.Opportunity.START,
this.#property
)
} }
getTalentAllocationAddition(talents) { getTalentAllocationAddition(talents) {
@@ -49,6 +58,10 @@ class Life {
const isEnd = this.#property.isEnd(); const isEnd = this.#property.isEnd();
const content = [talentContent, eventContent].flat(); const content = [talentContent, eventContent].flat();
this.#achievement.achieve(
this.#achievement.Opportunity.TRAJECTORY,
this.#property
)
return { age, content, isEnd }; return { age, content, isEnd };
} }
@@ -115,6 +128,10 @@ class Life {
} }
getSummary() { getSummary() {
this.#achievement.achieve(
this.#achievement.Opportunity.SUMMARY,
this.#property
)
return { return {
AGE: this.#property.get(this.#property.TYPES.HAGE), AGE: this.#property.get(this.#property.TYPES.HAGE),
CHR: this.#property.get(this.#property.TYPES.HCHR), CHR: this.#property.get(this.#property.TYPES.HCHR),
@@ -134,8 +151,18 @@ class Life {
return this.#talent.exclusive(talents, exclusive); return this.#talent.exclusive(talents, exclusive);
} }
getAchievements() {
return this.#achievement.list();
}
get times() { return this.#property?.get(this.#property.TYPES.TMS) || 0; } get times() { return this.#property?.get(this.#property.TYPES.TMS) || 0; }
set times(v) { return this.#property?.set(this.#property.TYPES.TMS, v) || 0; } set times(v) {
this.#property?.set(this.#property.TYPES.TMS, v) || 0;
this.#achievement.achieve(
this.#achievement.Opportunity.END,
this.#property
)
}
} }
export default Life; export default Life;

View File

@@ -38,10 +38,12 @@ class Property {
// Achievement Total // Achievement Total
ATLT: "ATLT", // 拥有过的天赋 Achieve Talent ATLT: "ATLT", // 拥有过的天赋 Achieve Talent
AEVT: "AEVT", // 触发过的事件 Achieve Event AEVT: "AEVT", // 触发过的事件 Achieve Event
ACHV: "ACHV", // 达成的成就 Achievement
}; };
#ageData; #ageData;
#data; #data = {};
initial({age}) { initial({age}) {
@@ -160,6 +162,7 @@ class Property {
return this.lsget('extendTalent') || null; return this.lsget('extendTalent') || null;
case this.TYPES.ATLT: case this.TYPES.ATLT:
case this.TYPES.AEVT: case this.TYPES.AEVT:
case this.TYPES.ACHV:
return this.lsget(prop) || []; return this.lsget(prop) || [];
default: return 0; default: return 0;
} }
@@ -289,9 +292,16 @@ class Property {
this.#data[h] = max(this.#data[h], value); this.#data[h] = max(this.#data[h], value);
} }
achieve(prop, newData = []) { achieve(prop, newData) {
let key; let key;
switch(prop) { switch(prop) {
case this.TYPES.ACHV:
const lastData = this.lsget(prop);
this.lsset(
prop,
(lastData || []).concat([[newData, Date.now()]])
);
return;
case this.TYPES.TLT: key = this.TYPES.ATLT; break; case this.TYPES.TLT: key = this.TYPES.ATLT; break;
case this.TYPES.EVT: key = this.TYPES.AEVT; break; case this.TYPES.EVT: key = this.TYPES.AEVT; break;
default: return; default: return;
@@ -302,7 +312,7 @@ class Property {
Array.from( Array.from(
new Set( new Set(
lastData lastData
.concat(newData) .concat(newData||[])
.flat() .flat()
) )
) )

View File

@@ -1,7 +1,30 @@
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import Life from '../src/life.js' import Life from '../src/life.js'
global.json = async fileName => JSON.parse(await readFile(`data/${fileName}.json`)); globalThis.json = async fileName => JSON.parse(await readFile(`data/${fileName}.json`));
globalThis.localStorage = {};
localStorage.getItem = key => localStorage[key]===void 0? null: localStorage[key];
localStorage.setItem = (key, value) => (localStorage[key] = value);
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);
}
async function debug() { async function debug() {