mirror of
https://github.com/VickScarlet/lifeRestart.git
synced 2025-07-22 12:03:56 +08:00
add achievement
This commit is contained in:
1334
data/achievement.json
Normal file
1334
data/achievement.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
data/achievement.xlsx
Normal file
BIN
data/achievement.xlsx
Normal file
Binary file not shown.
64
repl/app.js
64
repl/app.js
@@ -1,9 +1,26 @@
|
||||
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`));
|
||||
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() {
|
||||
@@ -47,7 +64,7 @@ class App {
|
||||
|
||||
async initial() {
|
||||
this.output('Now Loading...');
|
||||
this.#talentExtend = global.localStorage.talentExtend;
|
||||
this.#talentExtend = localStorage.talentExtend;
|
||||
await this.#life.initial();
|
||||
this.output(`\rLoading Complete.
|
||||
人生重开模拟器
|
||||
@@ -55,6 +72,11 @@ class App {
|
||||
\n🎉键入 \x1B[4m/remake\x1B[24m 开始游戏`,
|
||||
true
|
||||
);
|
||||
$$on('achievement', ({name})=>this.output(`
|
||||
-------------------------
|
||||
解锁成就【${name}】
|
||||
-------------------------
|
||||
`))
|
||||
}
|
||||
|
||||
io(input, output, exit) {
|
||||
@@ -289,7 +311,7 @@ class App {
|
||||
remake() {
|
||||
if(this.#talentExtend) {
|
||||
this.#life.talentExtend(this.#talentExtend)
|
||||
global.dumpLocalStorage();
|
||||
dumpLocalStorage();
|
||||
this.#talentExtend = null;
|
||||
}
|
||||
|
||||
@@ -549,34 +571,22 @@ class App {
|
||||
}
|
||||
|
||||
summary() {
|
||||
|
||||
const records = this.#life.getRecord();
|
||||
const s = (type, func)=>{
|
||||
const value = func(records.map(({[type]:v})=>v));
|
||||
const summaryData = this.#life.getSummary();
|
||||
const format = (name, type) => {
|
||||
const value = summaryData[type];
|
||||
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 this.style(`grade${grade}b`, `${name}:${value} ${judge}`);
|
||||
}
|
||||
|
||||
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 );
|
||||
})(),
|
||||
format('颜值', 'CHR'),
|
||||
format('智力', 'INT'),
|
||||
format('体质', 'STR'),
|
||||
format('家境', 'MNY'),
|
||||
format('快乐', 'SPR'),
|
||||
format('享年', 'AGE'),
|
||||
format('总评', 'SUM'),
|
||||
].join('\n');
|
||||
}
|
||||
}
|
||||
|
@@ -4,12 +4,14 @@ import { readFile, writeFile } from 'fs/promises';
|
||||
async function main() {
|
||||
|
||||
try {
|
||||
global.localStorage = JSON.parse(await readFile('__localStorage.json'));
|
||||
globalThis.localStorage = JSON.parse(await readFile('__localStorage.json'));
|
||||
} 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();
|
||||
app.io(
|
||||
|
63
src/achievement.js
Normal file
63
src/achievement.js
Normal 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;
|
23
src/app.js
23
src/app.js
@@ -1,4 +1,3 @@
|
||||
import { max, sum } from './functions/util.js';
|
||||
import { summary } from './functions/summary.js'
|
||||
import Life from './life.js'
|
||||
|
||||
@@ -26,7 +25,7 @@ class App{
|
||||
]);
|
||||
this.#specialthanks = specialthanks;
|
||||
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');
|
||||
}
|
||||
const keyDownCallback = (keyboardEvent) => {
|
||||
@@ -35,8 +34,8 @@ class App{
|
||||
pressEnterFunc && typeof pressEnterFunc === 'function' && pressEnterFunc();
|
||||
}
|
||||
}
|
||||
window.removeEventListener('keydown', keyDownCallback);
|
||||
window.addEventListener('keydown', keyDownCallback);
|
||||
globalThis.removeEventListener('keydown', keyDownCallback);
|
||||
globalThis.addEventListener('keydown', keyDownCallback);
|
||||
}
|
||||
|
||||
initPages() {
|
||||
@@ -109,8 +108,8 @@ class App{
|
||||
<ul class="g1"></ul>
|
||||
<ul class="g2"></ul>
|
||||
</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="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://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://dun.mianbaoduo.com/@vickscarlet')" style="background-color:#c69; left:50%; right:auto; transform: translate(2rem,-50%);">打赏程序(顿顿饭)</button>
|
||||
</div>
|
||||
`);
|
||||
|
||||
@@ -374,10 +373,10 @@ class App{
|
||||
const property = this.#life.getLastRecord();
|
||||
$("#lifeProperty").html(`
|
||||
<li><span>颜值</span><span>${property.CHR}</span></li>
|
||||
<li><span>智力</span><span>${property.INT}</span</li>
|
||||
<li><span>体质</span><span>${property.STR}</span</li>
|
||||
<li><span>家境</span><span>${property.MNY}</span</li>
|
||||
<li><span>快乐</span><span>${property.SPR}</span</li>
|
||||
<li><span>智力</span><span>${property.INT}</span></li>
|
||||
<li><span>体质</span><span>${property.STR}</span></li>
|
||||
<li><span>家境</span><span>${property.MNY}</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) {
|
||||
|
21
src/index.js
21
src/index.js
@@ -1,10 +1,27 @@
|
||||
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
|
||||
window.hideBanners = (e) => {
|
||||
globalThis.hideBanners = (e) => {
|
||||
document
|
||||
.querySelectorAll(".banner.visible")
|
||||
.forEach((b) => b.classList.remove("visible"));
|
||||
|
31
src/life.js
31
src/life.js
@@ -1,28 +1,33 @@
|
||||
import Property from './property.js';
|
||||
import Event from './event.js';
|
||||
import Talent from './talent.js';
|
||||
import Achievement from './achievement.js';
|
||||
|
||||
class Life {
|
||||
constructor() {
|
||||
this.#property = new Property();
|
||||
this.#event = new Event();
|
||||
this.#talent = new Talent();
|
||||
this.#achievement = new Achievement();
|
||||
}
|
||||
|
||||
#property;
|
||||
#event;
|
||||
#talent;
|
||||
#achievement;
|
||||
#triggerTalents;
|
||||
|
||||
async initial() {
|
||||
const [age, talents, events] = await Promise.all([
|
||||
const [age, talents, events, achievements] = await Promise.all([
|
||||
json('age'),
|
||||
json('talents'),
|
||||
json('events'),
|
||||
json('achievement'),
|
||||
])
|
||||
this.#property.initial({age});
|
||||
this.#talent.initial({talents});
|
||||
this.#event.initial({events});
|
||||
this.#achievement.initial({achievements});
|
||||
}
|
||||
|
||||
restart(allocation) {
|
||||
@@ -30,6 +35,10 @@ class Life {
|
||||
this.#property.restart(allocation);
|
||||
this.doTalent();
|
||||
this.#property.restartLastStep();
|
||||
this.#achievement.achieve(
|
||||
this.#achievement.Opportunity.START,
|
||||
this.#property
|
||||
)
|
||||
}
|
||||
|
||||
getTalentAllocationAddition(talents) {
|
||||
@@ -49,6 +58,10 @@ class Life {
|
||||
const isEnd = this.#property.isEnd();
|
||||
|
||||
const content = [talentContent, eventContent].flat();
|
||||
this.#achievement.achieve(
|
||||
this.#achievement.Opportunity.TRAJECTORY,
|
||||
this.#property
|
||||
)
|
||||
return { age, content, isEnd };
|
||||
}
|
||||
|
||||
@@ -115,6 +128,10 @@ class Life {
|
||||
}
|
||||
|
||||
getSummary() {
|
||||
this.#achievement.achieve(
|
||||
this.#achievement.Opportunity.SUMMARY,
|
||||
this.#property
|
||||
)
|
||||
return {
|
||||
AGE: this.#property.get(this.#property.TYPES.HAGE),
|
||||
CHR: this.#property.get(this.#property.TYPES.HCHR),
|
||||
@@ -134,8 +151,18 @@ class Life {
|
||||
return this.#talent.exclusive(talents, exclusive);
|
||||
}
|
||||
|
||||
getAchievements() {
|
||||
return this.#achievement.list();
|
||||
}
|
||||
|
||||
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;
|
||||
|
@@ -38,10 +38,12 @@ class Property {
|
||||
// Achievement Total
|
||||
ATLT: "ATLT", // 拥有过的天赋 Achieve Talent
|
||||
AEVT: "AEVT", // 触发过的事件 Achieve Event
|
||||
|
||||
ACHV: "ACHV", // 达成的成就 Achievement
|
||||
};
|
||||
|
||||
#ageData;
|
||||
#data;
|
||||
#data = {};
|
||||
|
||||
initial({age}) {
|
||||
|
||||
@@ -160,6 +162,7 @@ class Property {
|
||||
return this.lsget('extendTalent') || null;
|
||||
case this.TYPES.ATLT:
|
||||
case this.TYPES.AEVT:
|
||||
case this.TYPES.ACHV:
|
||||
return this.lsget(prop) || [];
|
||||
default: return 0;
|
||||
}
|
||||
@@ -289,9 +292,16 @@ class Property {
|
||||
this.#data[h] = max(this.#data[h], value);
|
||||
}
|
||||
|
||||
achieve(prop, newData = []) {
|
||||
achieve(prop, newData) {
|
||||
let key;
|
||||
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.EVT: key = this.TYPES.AEVT; break;
|
||||
default: return;
|
||||
@@ -302,7 +312,7 @@ class Property {
|
||||
Array.from(
|
||||
new Set(
|
||||
lastData
|
||||
.concat(newData)
|
||||
.concat(newData||[])
|
||||
.flat()
|
||||
)
|
||||
)
|
||||
|
@@ -1,7 +1,30 @@
|
||||
import { readFile } from 'fs/promises';
|
||||
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() {
|
||||
|
||||
|
Reference in New Issue
Block a user