├── CNAME ├── _config.yml ├── .jshintrc ├── data ├── age.xlsx ├── events.xlsx ├── talents.xlsx ├── talents.csv └── talents.json ├── view ├── iconfont.ttf ├── iconfont.woff ├── iconfont.woff2 ├── images │ └── icons │ │ ├── icon-72x72.png │ │ ├── icon-96x96.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ └── icon-512x512.png ├── sw.js ├── manifest.json ├── test.html ├── index.html ├── condition_test.html ├── light.css └── dark.css ├── index.md ├── src ├── index.js ├── functions │ ├── util.js │ ├── summary.js │ └── condition.js ├── event.js ├── talent.js ├── life.js ├── property.js └── app.js ├── package.json ├── repl ├── index.js └── app.js ├── .vscode └── launch.json ├── LICENSE ├── webpack.config.cjs ├── test └── index.js ├── .gitignore ├── _layouts └── default.html ├── README.md └── utils └── xlsxTransform.js /CNAME: -------------------------------------------------------------------------------- 1 | liferestart.syaro.io -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 9 3 | } -------------------------------------------------------------------------------- /data/age.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/data/age.xlsx -------------------------------------------------------------------------------- /data/events.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/data/events.xlsx -------------------------------------------------------------------------------- /data/talents.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/data/talents.xlsx -------------------------------------------------------------------------------- /view/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/iconfont.ttf -------------------------------------------------------------------------------- /view/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/iconfont.woff -------------------------------------------------------------------------------- /view/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/iconfont.woff2 -------------------------------------------------------------------------------- /view/images/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/images/icons/icon-72x72.png -------------------------------------------------------------------------------- /view/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /view/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /view/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /view/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /view/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /view/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /view/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliyunWorkbench/lifeRestart/HEAD/view/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | # Life Restart 2 | 3 | 人生重开模拟器,云开发平台一键部署。[RESTART](view/index.html) 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import App from '../src/app.js'; 2 | 3 | 4 | window.json = async fileName => await (await fetch(`./data/${fileName}.json`)).json(); 5 | 6 | // Pssst, I've created a github package - https://github.com/brookesb91/dismissible 7 | window.hideBanners = (e) => { 8 | document 9 | .querySelectorAll(".banner.visible") 10 | .forEach((b) => b.classList.remove("visible")); 11 | }; 12 | 13 | const app = new App(); 14 | app.initial(); -------------------------------------------------------------------------------- /view/sw.js: -------------------------------------------------------------------------------- 1 | var CACHE_VERSION = 'sw_v1'; 2 | 3 | var CACHE_FILES = [ 4 | '/', 5 | ]; 6 | 7 | self.addEventListener('install', function (event) { 8 | event.waitUntil( 9 | caches.open(CACHE_VERSION) 10 | .then(cache => cache.addAll(CACHE_FILES) 11 | .then(() => self.skipWaiting()) 12 | )); 13 | }); 14 | 15 | self.addEventListener('activate', function (event) { 16 | event.waitUntil( 17 | caches.keys().then(function (keys) { 18 | return Promise.all(keys.map(function (key, i) { 19 | if (key !== CACHE_VERSION) { 20 | return caches.delete(keys[i]); 21 | } 22 | })); 23 | }) 24 | ); 25 | }); 26 | 27 | self.addEventListener('fetch', function(event) {}); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xlsx_transform", 3 | "type": "module", 4 | "bin": "test/index.js", 5 | "scripts": { 6 | "test": "node test", 7 | "xlsxTransform": "node utils/xlsxTransform.js data", 8 | "dev": "webpack serve --open /view/index.html", 9 | "build": "webpack --mode production && cp -r ./view/* ./public/ && cp -r ./data ./public/ && mv public build" 10 | }, 11 | "dependencies": { 12 | "@babel/core": "^7.15.4", 13 | "@babel/preset-env": "^7.15.4", 14 | "babel-loader": "^8.2.2", 15 | "core-js": "^3.17.2", 16 | "webpack": "^5.51.2", 17 | "webpack-dev-server": "^4.1.0", 18 | "xlsx": "^0.17.0" 19 | }, 20 | "devDependencies": { 21 | "webpack-cli": "^4.8.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/functions/util.js: -------------------------------------------------------------------------------- 1 | function clone(value) { 2 | switch(typeof value) { 3 | case 'object': 4 | if(Array.isArray(value)) return value.map(v=>clone(v)); 5 | const newObj = {}; 6 | for(const key in value) newObj[key] = clone(value[key]); 7 | return newObj; 8 | default: return value; 9 | } 10 | } 11 | 12 | function max(...arr) { 13 | return Math.max(...arr.flat()); 14 | } 15 | 16 | function min(...arr) { 17 | return Math.min(...arr.flat()); 18 | } 19 | 20 | function sum(...arr) { 21 | let s = 0; 22 | arr.flat().forEach(v=>s+=v); 23 | return s; 24 | } 25 | 26 | function average(...arr) { 27 | const s = sum(...arr); 28 | return s / arr.flat().length; 29 | } 30 | 31 | export { clone, max, min, sum, average }; -------------------------------------------------------------------------------- /repl/index.js: -------------------------------------------------------------------------------- 1 | import App from './app.js'; 2 | import { readFile, writeFile } from 'fs/promises'; 3 | 4 | async function main() { 5 | 6 | try { 7 | global.localStorage = JSON.parse(await readFile('__localStorage.json')); 8 | } catch (e) { 9 | global.localStorage = {}; 10 | } 11 | 12 | global.dumpLocalStorage = async ()=>await writeFile('__localStorage.json', JSON.stringify( global.localStorage)) 13 | 14 | const app = new App(); 15 | app.io( 16 | repl => process.stdin.on('data', data=>repl(data.toString().trim())), 17 | (data, isRepl) => process.stdout.write(`${data}${isRepl?'\n>':''}`), 18 | code=>process.exit(code) 19 | ) 20 | await app.initial(); 21 | } 22 | 23 | main(); 24 | 25 | // process.stdin.setRawMode(true); 26 | 27 | // process.openStdin().on('keypress', function (chunk, key) { 28 | // process.stdout.write('Get Chunk: ' + chunk + '\n'); 29 | // if (key && key.ctrl && key.name == 'c') process.exit(); 30 | // }); -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "test", 11 | "program": "${workspaceFolder}/test", 12 | "skipFiles": [ 13 | "/**" 14 | ] 15 | }, 16 | { 17 | "type": "node", 18 | "request": "launch", 19 | "name": "xlsxTransform", 20 | "program": "${workspaceFolder}/utils/xlsxTransform.js", 21 | "args": ["data"], 22 | "skipFiles": [ 23 | "/**" 24 | ] 25 | }, 26 | { 27 | "name": "Attach by Process ID", 28 | "processId": "${command:PickProcess}", 29 | "request": "attach", 30 | "skipFiles": [ 31 | "/**" 32 | ], 33 | "type": "node" 34 | }, 35 | ] 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 神戸小鳥 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /webpack.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: './src/index.js', 6 | devtool: 'eval-cheap-module-source-map', 7 | devServer: { 8 | static: [ 9 | { 10 | directory: path.join(__dirname, 'data'), 11 | publicPath: '/data', 12 | }, 13 | { 14 | directory: path.join(__dirname, 'public'), 15 | publicPath: '/public', 16 | }, 17 | { 18 | directory: path.join(__dirname, 'view'), 19 | publicPath: '/view', 20 | }, 21 | ], 22 | }, 23 | output: { 24 | path: path.resolve(__dirname, 'public'), 25 | filename: 'bundle.js', 26 | clean: true, 27 | }, 28 | // resolve: { 29 | // extensions: ['.js'], 30 | // }, 31 | module: { 32 | rules: [{ 33 | test: /\.js$/, 34 | exclude: /node_modules/, 35 | use: { 36 | loader: 'babel-loader', 37 | options: { 38 | presets: [ 39 | [ 40 | '@babel/preset-env', 41 | { 42 | "targets": "> 0.25%, not dead", 43 | "useBuiltIns": "usage", 44 | "corejs": "3.8.3", 45 | } 46 | ] 47 | ] 48 | } 49 | } 50 | }] 51 | } 52 | }; -------------------------------------------------------------------------------- /view/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "icons": [ 3 | { 4 | "src": "./images/icons/icon-72x72.png", 5 | "sizes": "72x72", 6 | "type": "image/png" 7 | }, 8 | { 9 | "src": "./images/icons/icon-96x96.png", 10 | "sizes": "96x96", 11 | "type": "image/png" 12 | }, 13 | { 14 | "src": "./images/icons/icon-128x128.png", 15 | "sizes": "128x128", 16 | "type": "image/png" 17 | }, 18 | { 19 | "src": "./images/icons/icon-144x144.png", 20 | "sizes": "144x144", 21 | "type": "image/png" 22 | }, 23 | { 24 | "src": "./images/icons/icon-152x152.png", 25 | "sizes": "152x152", 26 | "type": "image/png" 27 | }, 28 | { 29 | "src": "./images/icons/icon-192x192.png", 30 | "sizes": "192x192", 31 | "type": "image/png" 32 | }, 33 | { 34 | "src": "./images/icons/icon-384x384.png", 35 | "sizes": "384x384", 36 | "type": "image/png" 37 | }, 38 | { 39 | "src": "./images/icons/icon-512x512.png", 40 | "sizes": "512x512", 41 | "type": "image/png" 42 | } 43 | ], 44 | "name": "人生重开模拟器", 45 | "short_name": "LifeRestart", 46 | "orientation": "portrait", 47 | "display": "standalone", 48 | "start_url": "/view/index.html", 49 | "description": "人生重开模拟器", 50 | "background_color": "#ffffff" 51 | } -------------------------------------------------------------------------------- /src/event.js: -------------------------------------------------------------------------------- 1 | import { clone } from './functions/util.js'; 2 | import { checkCondition } from './functions/condition.js'; 3 | 4 | class Event { 5 | constructor() {} 6 | 7 | #events; 8 | 9 | initial({events}) { 10 | this.#events = events; 11 | for(const id in events) { 12 | const event = events[id]; 13 | if(!event.branch) continue; 14 | event.branch = event.branch.map(b=>{ 15 | b = b.split(':'); 16 | b[1] = Number(b[1]); 17 | return b; 18 | }); 19 | } 20 | } 21 | 22 | check(eventId, property) { 23 | const { include, exclude, NoRandom } = this.get(eventId); 24 | if(NoRandom) return false; 25 | if(exclude && checkCondition(property, exclude)) return false; 26 | if(include) return checkCondition(property, include); 27 | return true; 28 | } 29 | 30 | get(eventId) { 31 | const event = this.#events[eventId]; 32 | if(!event) throw new Error(`[ERROR] No Event[${eventId}]`); 33 | return clone(event); 34 | } 35 | 36 | information(eventId) { 37 | const { event: description } = this.get(eventId) 38 | return { description }; 39 | } 40 | 41 | do(eventId, property) { 42 | const { effect, branch, event: description, postEvent } = this.get(eventId); 43 | if(branch) 44 | for(const [cond, next] of branch) 45 | if(checkCondition(property, cond)) 46 | return { effect, next, description }; 47 | return { effect, postEvent, description }; 48 | } 49 | 50 | } 51 | 52 | export default Event; -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs/promises'; 2 | import Life from '../src/life.js' 3 | 4 | global.json = async fileName => JSON.parse(await readFile(`data/${fileName}.json`)); 5 | 6 | async function debug() { 7 | 8 | const life = new Life(); 9 | await life.initial(); 10 | 11 | life.restart({ 12 | CHR: 5, // 颜值 charm CHR 13 | INT: 5, // 智力 intelligence INT 14 | STR: 5, // 体质 strength STR 15 | MNY: 5, // 家境 money MNY 16 | SPR: 5, // 快乐 spirit SPR 17 | TLT: [1004, 1005, 1009], // 天赋 talent TLT 18 | }); 19 | const lifeTrajectory = []; 20 | let trajectory; 21 | do{ 22 | try{ 23 | trajectory = life.next(); 24 | } catch(e) { 25 | console.error(e); 26 | // debugger 27 | throw e; 28 | } 29 | lifeTrajectory.push(lifeTrajectory); 30 | const { age, content } = trajectory; 31 | console.debug( 32 | `---------------------------------`, 33 | `\n-- ${age} 岁\n `, 34 | content.map( 35 | ({type, description, rate, name, postEvent}) => { 36 | switch(type) { 37 | case 'TLT': 38 | return `天赋【${name}】发动:${description}`; 39 | case 'EVT': 40 | return description + (postEvent?`\n ${postEvent}`:''); 41 | } 42 | } 43 | ).join('\n ') 44 | ); 45 | } while(!trajectory.isEnd) 46 | // debugger; 47 | } 48 | 49 | debug(); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | utils/xlsxTransform-* 107 | 108 | /.idea 109 | 110 | __localStorage.json 111 | 112 | build -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% seo %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% include head-custom.html %} 14 | 15 | 16 | Skip to the content. 17 | 18 | 31 | 32 |
33 | {{ content }} 34 | 35 | 41 |
42 | 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 人生重开模拟器游戏 3 | 4 | ## 一、游戏简介 5 | 6 | 本项目可将人生重开模拟器游戏,一键部署至云开发平台。项目原地址:https://github.com/VickScarlet/lifeRestart 7 | 8 | 项目在源码基础上,仅修改了package.json中的build指令和部分js的相对引用路径,适配项目部署到云端时需要的路径规范。(不修改亦可正常部署,但需将「部署配置」中的「资源路径」修改为“.”,且需要在域名后增加访问路径,并不优雅) 9 | 10 | ## 二、部署到云开发平台 11 | 12 | **1.创建lifeRestart代码项目** 13 | 14 | 直接fork本项目到自己的GitHub账号下。 15 | 16 | **2.打开云开发平台,完成阿里云账号注册登陆,同意云开发平台服务协议** https://workbench.aliyun.com/application 17 | 18 | 19 | 20 | **3.创建云开发平台-前端部署应用** 21 | 22 | 3.1 创建前端应用 23 | 24 | 依次点击「应用列表」「前端应用」「新建前端应用」按钮。首先绑定GitHub帐号,允许云开发平台构建、发布你的GitHub代码为可访问的网站。 25 | 26 | 27 | 28 | 29 | 30 | 选择第一步中的代码仓库、主干分支等,并点击下一步。主干分支一般指的是代码的master或main等分支。 31 | 32 | 33 | 34 | 点击「下一步」、「完成」。稍等片刻创建成功后,将进入到应用部署界面。 35 | 36 | 37 | 38 | 3.2 进行项目的部署 39 | 40 | 依次点击日常环境的「部署」「确定」,即可启动日常环境的发布流程。 41 | 42 | 43 | 44 | 如果您是第一次在该仓库上使用云开发平台,需要将 https://ram.console.aliyun.com/manage/ak 中的AccessKeyID、AccessKeySecret,粘贴配置到GitHub Secret中的AK、SK变量中。具体步骤,请仔细阅读「部署配置」的Step1。 45 | 46 | 47 | 48 | 3.3 查看部署结果 49 | 50 | 部署成功后,点击「部署配置」,并点击「测试域名」,即可访问您的应用。请注意测试域名的过期时间。 51 | 52 | 53 | 54 | 如果测试域名访问时出现类似以下情况,请点击「高级」-「继续访问」。这是因为临时测试域名并未提供完整的证书,仅供您临时测试使用。如果需要发布为长期的正式网站,请看下一步。 55 | 56 | 57 | 58 | 3.4 (可选)添加自己的自定义域名 59 | 60 | 但是您可以通过设置自定义域名,并进行CNAME解析,来持久化这个前端游戏。在「部署配置」「自定义域名」中,添加您自己名下的域名,重新点击部署。再按照提示,将您名下的域名CNAME到指定的OSS域名下,即可使用自己的域名,持续访问该应用。 61 | 62 | 63 | 64 | 3.5 (可选)使用CDN加速域名访问,节约流量费用 65 | 66 | 当网站流量巨大时,可点击「部署配置」中的「如何配置CDN加速」,将自己的域名与CDN加速绑定,从而加速网站访问,节约流量费用。 67 | 68 | ## 三、应用下线 69 | 70 | 云开发平台功能完全免费,但OSS存储会收取您存储、上传、下载的流量费用,具体请见: https://help.aliyun.com/document_detail/173521.html 71 | 72 | 如果希望马上停止应用计费,目前请您在OSS控制台指定Bucket内进行手动操作: https://oss.console.aliyun.com/bucket 73 | 74 | 进入您在「部署配置」中选择的Bucket,点击「文件管理」,并在多选框中勾选所有存储的文件,点击「删除」,即可即时完全停止应用被外界访问。 75 | 76 | 77 | 78 | 当您需要启动时,只要重新点击云开发平台的部署按钮即可开始部署。云开发平台会尽快增加一键停服的自动化功能,方便您随时暂停应用。 79 | -------------------------------------------------------------------------------- /src/talent.js: -------------------------------------------------------------------------------- 1 | import { clone } from './functions/util.js'; 2 | import { checkCondition } from './functions/condition.js'; 3 | 4 | class Talent { 5 | constructor() {} 6 | 7 | #talents; 8 | 9 | initial({talents}) { 10 | this.#talents = talents; 11 | for(const id in talents) { 12 | const talent = talents[id]; 13 | talent.id= Number(id); 14 | talent.grade = Number(talent.grade); 15 | } 16 | } 17 | 18 | check(talentId, property) { 19 | const { condition } = this.get(talentId); 20 | return checkCondition(property, condition); 21 | } 22 | 23 | get(talentId) { 24 | const talent = this.#talents[talentId]; 25 | if(!talent) throw new Error(`[ERROR] No Talent[${talentId}]`); 26 | return clone(talent); 27 | } 28 | 29 | information(talentId) { 30 | const { grade, name, description } = this.get(talentId) 31 | return { grade, name, description }; 32 | } 33 | 34 | exclusive(talends, exclusiveId) { 35 | const { exclusive } = this.get(exclusiveId); 36 | if(!exclusive) return null; 37 | for(const talent of talends) { 38 | for(const e of exclusive) { 39 | if(talent == e) return talent; 40 | } 41 | } 42 | return null; 43 | } 44 | 45 | talentRandom(include) { 46 | // 1000, 100, 10, 1 47 | const talentList = {}; 48 | for(const talentId in this.#talents) { 49 | const { id, grade, name, description } = this.#talents[talentId]; 50 | if(id == include) { 51 | include = { grade, name, description, id }; 52 | continue; 53 | } 54 | if(!talentList[grade]) talentList[grade] = [{ grade, name, description, id }]; 55 | else talentList[grade].push({ grade, name, description, id }); 56 | } 57 | 58 | return new Array(10) 59 | .fill(1).map((v, i)=>{ 60 | if(!i && include) return include; 61 | const gradeRandom = Math.random(); 62 | let grade; 63 | if(gradeRandom>=0.111) grade = 0; 64 | else if(gradeRandom>=0.011) grade = 1; 65 | else if(gradeRandom>=0.001) grade = 2; 66 | else grade = 3; 67 | 68 | while(talentList[grade].length == 0) grade--; 69 | 70 | const length = talentList[grade].length; 71 | 72 | const random = Math.floor(Math.random()*length) % length; 73 | return talentList[grade].splice(random,1)[0]; 74 | }); 75 | } 76 | 77 | allocationAddition(talents) { 78 | if(Array.isArray(talents)) { 79 | let addition = 0; 80 | for(const talent of talents) 81 | addition += this.allocationAddition(talent); 82 | return addition; 83 | } 84 | return Number(this.get(talents).status) || 0; 85 | } 86 | 87 | do(talentId, property) { 88 | const { effect, condition, grade, name, description } = this.get(talentId); 89 | if(condition && !checkCondition(property, condition)) 90 | return null; 91 | return { effect, grade, name, description }; 92 | } 93 | } 94 | 95 | export default Talent; -------------------------------------------------------------------------------- /src/functions/summary.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | "CHR": [ 3 | {"judge": "地狱", "grade": 0}, 4 | {"min":1, "judge": "折磨", "grade": 0}, 5 | {"min":2, "judge": "不佳", "grade": 0}, 6 | {"min":4, "judge": "普通", "grade": 0}, 7 | {"min":7, "judge": "优秀", "grade": 1}, 8 | {"min":9, "judge": "罕见", "grade": 2}, 9 | {"min":11, "judge": "逆天", "grade": 3}, 10 | ], 11 | "MNY": [ 12 | {"judge": "地狱", "grade": 0}, 13 | {"min":1, "judge": "折磨", "grade": 0}, 14 | {"min":2, "judge": "不佳", "grade": 0}, 15 | {"min":4, "judge": "普通", "grade": 0}, 16 | {"min":7, "judge": "优秀", "grade": 1}, 17 | {"min":9, "judge": "罕见", "grade": 2}, 18 | {"min":11, "judge": "逆天", "grade": 3}, 19 | ], 20 | "SPR": [ 21 | {"judge": "地狱", "grade": 0}, 22 | {"min":1, "judge": "折磨", "grade": 0}, 23 | {"min":2, "judge": "不幸", "grade": 0}, 24 | {"min":4, "judge": "普通", "grade": 0}, 25 | {"min":7, "judge": "幸福", "grade": 1}, 26 | {"min":9, "judge": "极乐", "grade": 2}, 27 | {"min":11, "judge": "天命", "grade": 3}, 28 | ], 29 | "INT": [ 30 | {"judge": "地狱", "grade": 0}, 31 | {"min":1, "judge": "折磨", "grade": 0}, 32 | {"min":2, "judge": "不佳", "grade": 0}, 33 | {"min":4, "judge": "普通", "grade": 0}, 34 | {"min":7, "judge": "优秀", "grade": 1}, 35 | {"min":9, "judge": "罕见", "grade": 2}, 36 | {"min":11, "judge": "逆天", "grade": 3}, 37 | {"min":21, "judge": "识海", "grade": 3}, 38 | {"min":131, "judge": "元神", "grade": 3}, 39 | {"min":501, "judge": "仙魂", "grade": 3}, 40 | ], 41 | "STR": [ 42 | {"judge": "地狱", "grade": 0}, 43 | {"min":1, "judge": "折磨", "grade": 0}, 44 | {"min":2, "judge": "不佳", "grade": 0}, 45 | {"min":4, "judge": "普通", "grade": 0}, 46 | {"min":7, "judge": "优秀", "grade": 1}, 47 | {"min":9, "judge": "罕见", "grade": 2}, 48 | {"min":11, "judge": "逆天", "grade": 3}, 49 | {"min":21, "judge": "凝气", "grade": 3}, 50 | {"min":101, "judge": "筑基", "grade": 3}, 51 | {"min":401, "judge": "金丹", "grade": 3}, 52 | {"min":1001, "judge": "元婴", "grade": 3}, 53 | {"min":2001, "judge": "仙体", "grade": 3}, 54 | ], 55 | "AGE": [ 56 | {"judge": "胎死腹中", "grade": 0}, 57 | {"min":1, "judge": "早夭", "grade": 0}, 58 | {"min":10, "judge": "少年", "grade": 0}, 59 | {"min":18, "judge": "盛年", "grade": 0}, 60 | {"min":40, "judge": "中年", "grade": 0}, 61 | {"min":60, "judge": "花甲", "grade": 1}, 62 | {"min":70, "judge": "古稀", "grade": 1}, 63 | {"min":80, "judge": "杖朝", "grade": 2}, 64 | {"min":90, "judge": "南山", "grade": 2}, 65 | {"min":95, "judge": "不老", "grade": 3}, 66 | {"min":100, "judge": "修仙", "grade": 3}, 67 | {"min":500, "judge": "仙寿", "grade": 3}, 68 | ], 69 | "SUM": [ 70 | {"judge": "地狱", "grade": 0}, 71 | {"min":41, "judge": "折磨", "grade": 0}, 72 | {"min":50, "judge": "不佳", "grade": 0}, 73 | {"min":60, "judge": "普通", "grade": 0}, 74 | {"min":80, "judge": "优秀", "grade": 1}, 75 | {"min":100, "judge": "罕见", "grade": 2}, 76 | {"min":110, "judge": "逆天", "grade": 3}, 77 | {"min":120, "judge": "传说", "grade": 3}, 78 | ] 79 | } 80 | 81 | function summary(type, value) { 82 | let length = data[type].length; 83 | while(length--) { 84 | const {min, judge, grade} = data[type][length]; 85 | if(min==void 0 || value >= min) return {judge, grade}; 86 | } 87 | } 88 | 89 | export { summary }; -------------------------------------------------------------------------------- /src/functions/condition.js: -------------------------------------------------------------------------------- 1 | function parseCondition(condition) { 2 | 3 | const conditions = []; 4 | const length = condition.length; 5 | const stack = []; 6 | stack.unshift(conditions); 7 | let cursor = 0; 8 | const catchString = i => { 9 | const str = condition.substring(cursor, i).trim(); 10 | cursor = i; 11 | if(str) stack[0].push(str); 12 | }; 13 | 14 | for(let i=0; i<\!\?=]/); 76 | 77 | const prop = condition.substring(0,i); 78 | const symbol = condition.substring(i, i+=(condition[i+1]=='='?2:1)); 79 | const d = condition.substring(i, length); 80 | 81 | const propData = property.get(prop); 82 | const conditionData = d[0]=='['? JSON.parse(d): Number(d); 83 | 84 | switch(symbol) { 85 | case '>': return propData > conditionData; 86 | case '<': return propData < conditionData; 87 | case '>=': return propData >= conditionData; 88 | case '<=': return propData <= conditionData; 89 | case '=': 90 | if(Array.isArray(propData)) 91 | return propData.includes(conditionData); 92 | return propData == conditionData; 93 | case '!=': 94 | if(Array.isArray(propData)) 95 | return !propData.includes(conditionData); 96 | return propData == conditionData; 97 | case '?': 98 | if(Array.isArray(propData)) { 99 | for(const p of propData) 100 | if(conditionData.includes(p)) return true; 101 | return false; 102 | } 103 | return conditionData.includes(propData); 104 | case '!': 105 | if(Array.isArray(propData)) { 106 | for(const p of propData) 107 | if(conditionData.includes(p)) return false; 108 | return true; 109 | } 110 | return !conditionData.includes(propData); 111 | 112 | default: return false; 113 | } 114 | } 115 | 116 | export { checkCondition }; -------------------------------------------------------------------------------- /src/life.js: -------------------------------------------------------------------------------- 1 | import Property from './property.js'; 2 | import Event from './event.js'; 3 | import Talent from './talent.js'; 4 | 5 | class Life { 6 | constructor() { 7 | this.#property = new Property(); 8 | this.#event = new Event(); 9 | this.#talent = new Talent(); 10 | } 11 | 12 | #property; 13 | #event; 14 | #talent; 15 | #triggerTalents; 16 | 17 | async initial() { 18 | const [age, talents, events] = await Promise.all([ 19 | json('age'), 20 | json('talents'), 21 | json('events'), 22 | ]) 23 | this.#property.initial({age}); 24 | this.#talent.initial({talents}); 25 | this.#event.initial({events}); 26 | } 27 | 28 | restart(allocation) { 29 | this.#triggerTalents = new Set(); 30 | this.#property.restart(allocation); 31 | this.doTalent(); 32 | this.#property.record(); 33 | } 34 | 35 | getTalentAllocationAddition(talents) { 36 | return this.#talent.allocationAddition(talents); 37 | } 38 | 39 | next() { 40 | const {age, event, talent} = this.#property.ageNext(); 41 | 42 | const talentContent = this.doTalent(talent); 43 | const eventContent = this.doEvent(this.random(event)); 44 | this.#property.record(); 45 | 46 | const isEnd = this.#property.isEnd(); 47 | 48 | const content = [talentContent, eventContent].flat(); 49 | return { age, content, isEnd }; 50 | } 51 | 52 | doTalent(talents) { 53 | if(talents) this.#property.change(this.#property.TYPES.TLT, talents); 54 | talents = this.#property.get(this.#property.TYPES.TLT) 55 | .filter(talentId=>!this.#triggerTalents.has(talentId)); 56 | 57 | const contents = []; 58 | for(const talentId of talents) { 59 | const result = this.#talent.do(talentId, this.#property); 60 | if(!result) continue; 61 | this.#triggerTalents.add(talentId); 62 | const { effect, name, description, grade } = result; 63 | contents.push({ 64 | type: this.#property.TYPES.TLT, 65 | name, 66 | grade, 67 | description, 68 | }) 69 | if(!effect) continue; 70 | this.#property.effect(effect); 71 | } 72 | return contents; 73 | } 74 | 75 | doEvent(eventId) { 76 | const { effect, next, description, postEvent } = this.#event.do(eventId, this.#property); 77 | this.#property.change(this.#property.TYPES.EVT, eventId); 78 | this.#property.effect(effect); 79 | const content = { 80 | type: this.#property.TYPES.EVT, 81 | description, 82 | postEvent, 83 | } 84 | if(next) return [content, this.doEvent(next)].flat(); 85 | return [content]; 86 | } 87 | 88 | random(events) { 89 | events = events.filter(([eventId])=>this.#event.check(eventId, this.#property)); 90 | 91 | let totalWeights = 0; 92 | for(const [, weight] of events) 93 | totalWeights += weight; 94 | 95 | let random = Math.random() * totalWeights; 96 | for(const [eventId, weight] of events) 97 | if((random-=weight)<0) 98 | return eventId; 99 | return events[events.length-1]; 100 | } 101 | 102 | talentRandom() { 103 | return this.#talent.talentRandom(JSON.parse(localStorage.extendTalent||'null')); 104 | } 105 | 106 | talentExtend(talentId) { 107 | localStorage.extendTalent = JSON.stringify(talentId); 108 | } 109 | 110 | getRecord() { 111 | return this.#property.getRecord(); 112 | } 113 | 114 | getLastRecord() { 115 | return this.#property.getLastRecord(); 116 | } 117 | 118 | exclusive(talents, exclusive) { 119 | return this.#talent.exclusive(talents, exclusive); 120 | } 121 | } 122 | 123 | export default Life; 124 | 125 | -------------------------------------------------------------------------------- /utils/xlsxTransform.js: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile, stat, readdir } from 'fs/promises'; 2 | import * as XLSX from 'xlsx'; 3 | import { join, extname, dirname } from 'path'; 4 | 5 | // const { readFile, writeFile, stat, readdir } = require('fs/promises'); 6 | // const XLSX = require('xlsx'); 7 | // const { join, extname, dirname } = require('path'); 8 | 9 | async function transform(filePath) { 10 | const xlsxFileBuffer = await readFile(filePath); 11 | const xlsx = XLSX.read(xlsxFileBuffer, {type: 'buffer'}); 12 | const sheets = xlsx.Sheets; 13 | 14 | const data = {}; 15 | for(const sheetName in sheets) { 16 | const sheetRawData = sheets[sheetName]; 17 | if(!sheetRawData['!ref']) break; 18 | const rawData = XLSX.utils.sheet_to_json(sheetRawData); 19 | const newData = {}; 20 | data[sheetName] = newData; 21 | rawData.shift(); 22 | for(const row of rawData) { 23 | const rowData = {}; 24 | let mainKey; 25 | for(let key in row) { 26 | const cell = row[key]; 27 | if(key[0] == "$") { 28 | key = key.substr(1); 29 | mainKey = cell; 30 | } 31 | if(key.includes(':')) { 32 | const keys = key.split(':'); 33 | const lastKey = keys.pop(); 34 | let temp = rowData; 35 | for(const subKey of keys) { 36 | if(!temp[subKey]) temp[subKey] = {}; 37 | temp = temp[subKey]; 38 | } 39 | if(lastKey.includes('[]')) { 40 | const aKey = lastKey.split('[]')[0]; 41 | if(!temp[aKey]) temp[aKey] = [cell]; 42 | else temp[aKey].push(cell); 43 | } else { 44 | temp[lastKey] = cell; 45 | } 46 | } else if(key.includes('[]')) { 47 | const aKey = key.split('[]')[0]; 48 | if(!rowData[aKey]) rowData[aKey] = [cell]; 49 | else rowData[aKey].push(cell); 50 | } else { 51 | rowData[key] = cell; 52 | } 53 | } 54 | if(mainKey===undefined) return console.error('No Main Key', rowData); 55 | newData[mainKey] = rowData; 56 | } 57 | } 58 | return data; 59 | } 60 | 61 | async function walk(filePath) { 62 | const xlsxPaths = []; 63 | if(Array.isArray(filePath)) { 64 | for(const subPath of filePath) 65 | xlsxPaths.push(await walk(subPath)); 66 | return xlsxPaths.flat(); 67 | } 68 | const fileStat = await stat(filePath); 69 | if(!fileStat.isDirectory()) { 70 | const ext = extname(filePath); 71 | if( ext=='.xls' || ext=='.xlsx' ) xlsxPaths.push(filePath); 72 | return xlsxPaths; 73 | } 74 | 75 | const dirData = await readdir(filePath); 76 | for(const subPath of dirData) 77 | xlsxPaths.push(await walk(join(filePath, subPath))); 78 | return xlsxPaths.flat(); 79 | } 80 | 81 | async function main() { 82 | const filePaths = process.argv.slice(2); 83 | if(filePaths.length<0) process.exit(0); 84 | const xlsxs = await walk(filePaths); 85 | for(const p of xlsxs) { 86 | const data = await transform(p); 87 | const d = dirname(p); 88 | for(const sheetName in data) { 89 | const savePath = join(d, `${sheetName}.json`); 90 | console.info(`[Transform] XLSX(${p}:${sheetName}) -> JSON(${savePath})`); 91 | await writeFile( 92 | savePath, 93 | JSON.stringify(data[sheetName], null, 4), 94 | ); 95 | } 96 | } 97 | console.info(` 98 | ------------------------ 99 | | Transform Complete | 100 | ------------------------ 101 | `); 102 | setTimeout(()=>{}, 1000); 103 | } 104 | 105 | main(); -------------------------------------------------------------------------------- /src/property.js: -------------------------------------------------------------------------------- 1 | import { clone } from './functions/util.js'; 2 | 3 | class Property { 4 | constructor() {} 5 | 6 | TYPES = { 7 | AGE: "AGE", 8 | CHR: "CHR", 9 | INT: "INT", 10 | STR: "STR", 11 | MNY: "MNY", 12 | SPR: "SPR", 13 | LIF: "LIF", 14 | TLT: "TLT", 15 | EVT: "EVT", 16 | }; 17 | 18 | #ageData; 19 | #data; 20 | #record; 21 | 22 | initial({age}) { 23 | 24 | this.#ageData = age; 25 | for(const a in age) { 26 | let { event, talent } = age[a]; 27 | if(!Array.isArray(event)) 28 | event = event?.split(',') || []; 29 | 30 | event = event.map(v=>{ 31 | const value = `${v}`.split('*').map(n=>Number(n)); 32 | if(value.length==1) value.push(1); 33 | return value; 34 | }); 35 | 36 | if(!Array.isArray(talent)) 37 | talent = talent?.split(',') || []; 38 | 39 | talent = talent.map(v=>Number(v)); 40 | 41 | age[a] = { event, talent }; 42 | } 43 | } 44 | 45 | restart(data) { 46 | this.#data = { 47 | [this.TYPES.AGE]: -1, 48 | [this.TYPES.CHR]: 0, 49 | [this.TYPES.INT]: 0, 50 | [this.TYPES.STR]: 0, 51 | [this.TYPES.MNY]: 0, 52 | [this.TYPES.SPR]: 0, 53 | [this.TYPES.LIF]: 1, 54 | [this.TYPES.TLT]: [], 55 | [this.TYPES.EVT]: [], 56 | }; 57 | for(const key in data) 58 | this.change(key, data[key]); 59 | this.#record = []; 60 | } 61 | 62 | get(prop) { 63 | switch(prop) { 64 | case this.TYPES.AGE: 65 | case this.TYPES.CHR: 66 | case this.TYPES.INT: 67 | case this.TYPES.STR: 68 | case this.TYPES.MNY: 69 | case this.TYPES.SPR: 70 | case this.TYPES.LIF: 71 | case this.TYPES.TLT: 72 | case this.TYPES.EVT: 73 | return clone(this.#data[prop]); 74 | default: return 0; 75 | } 76 | } 77 | 78 | set(prop, value) { 79 | switch(prop) { 80 | case this.TYPES.AGE: 81 | case this.TYPES.CHR: 82 | case this.TYPES.INT: 83 | case this.TYPES.STR: 84 | case this.TYPES.MNY: 85 | case this.TYPES.SPR: 86 | case this.TYPES.LIF: 87 | case this.TYPES.TLT: 88 | case this.TYPES.EVT: 89 | this.#data[prop] = clone(value); 90 | break; 91 | default: return 0; 92 | } 93 | } 94 | 95 | record() { 96 | this.#record.push({ 97 | [this.TYPES.AGE]: this.get(this.TYPES.AGE), 98 | [this.TYPES.CHR]: this.get(this.TYPES.CHR), 99 | [this.TYPES.INT]: this.get(this.TYPES.INT), 100 | [this.TYPES.STR]: this.get(this.TYPES.STR), 101 | [this.TYPES.MNY]: this.get(this.TYPES.MNY), 102 | [this.TYPES.SPR]: this.get(this.TYPES.SPR), 103 | }); 104 | } 105 | 106 | getRecord() { 107 | return clone(this.#record); 108 | } 109 | 110 | getLastRecord() { 111 | return clone(this.#record[this.#record.length - 1]); 112 | } 113 | 114 | change(prop, value) { 115 | if(Array.isArray(value)) { 116 | for(const v of value) 117 | this.change(prop, Number(v)); 118 | return; 119 | } 120 | switch(prop) { 121 | case this.TYPES.AGE: 122 | case this.TYPES.CHR: 123 | case this.TYPES.INT: 124 | case this.TYPES.STR: 125 | case this.TYPES.MNY: 126 | case this.TYPES.SPR: 127 | case this.TYPES.LIF: 128 | this.#data[prop] += Number(value); 129 | break; 130 | case this.TYPES.TLT: 131 | case this.TYPES.EVT: 132 | const v = this.#data[prop]; 133 | if(value<0) { 134 | const index = v.indexOf(value); 135 | if(index!=-1) v.splice(index,1); 136 | } 137 | if(!v.includes(value)) v.push(value); 138 | break; 139 | default: return; 140 | } 141 | } 142 | 143 | effect(effects) { 144 | for(const prop in effects) 145 | this.change(prop, Number(effects[prop])); 146 | } 147 | 148 | isEnd() { 149 | return this.get(this.TYPES.LIF) < 1; 150 | } 151 | 152 | ageNext() { 153 | this.change(this.TYPES.AGE, 1); 154 | const age = this.get(this.TYPES.AGE); 155 | const {event, talent} = this.getAgeData(age); 156 | return {age, event, talent}; 157 | } 158 | 159 | getAgeData(age) { 160 | return clone(this.#ageData[age]); 161 | } 162 | 163 | } 164 | 165 | export default Property; -------------------------------------------------------------------------------- /data/talents.csv: -------------------------------------------------------------------------------- 1 | $id,name,description,condition,grade,status,effect:SPR,effect:MNY,effect:CHR,effect:STR,effect:INT,exclusive[],exclusive[],exclusive[],exclusive[],exclusive[],exclusive[] 2 | 序号,天赋名,括号中的内容,触发条件,稀有度,初始可用属性点,额外快乐,额外家境,额外颜值,额外体质,额外智力,互斥天赋,互斥天赋,互斥天赋,互斥天赋,互斥天赋,互斥天赋 3 | 1001,随身玉佩,或许有护佑作用,,0,,,,,,,,,,,, 4 | 1002,红肚兜,小时候死亡率降低,,0,,,,,,,,,,,, 5 | 1003,生而为男,性别一定为男,,1,,,,,,,1004,1025,1024,1113,, 6 | 1004,生而为女,性别一定为女,,1,,,,,,,1003,1024,1025,,, 7 | 1005,动漫高手,入宅的可能性翻6倍,,2,,,,,,,,,,,, 8 | 1006,乐天派,快乐+1,,0,,1,,,,,,,,,, 9 | 1007,天赋异禀,初始可用属性点+2,,1,2,,,,,,,,,,, 10 | 1008,天生抑郁,快乐-3,,0,,-3,,,,,,,,,, 11 | 1009,网络巨魔,快乐+2,,1,,2,,,,,,,,,, 12 | 1010,天龙人,你拥有北京户口,,2,,,,,,,1012,1013,1014,,, 13 | 1011,独生子女,你没有兄弟姐妹,,0,,,,,,,,,,,, 14 | 1012,乡间微风,你出生在农村,,0,,,,,,,1010,1013,1014,,, 15 | 1013,城中高楼,你出生在城市,,0,,,,,,,1010,1012,1014,,, 16 | 1014,美籍华人,你有美国国籍,,2,,,3,,,,1010,1012,1013,,, 17 | 1015,家中老大,你最受父母宠爱,,1,,,,,,,,,,,, 18 | 1016,水性良好,不会被淹死,,0,,,,,,,,,,,, 19 | 1017,先天免疫,你不会得艾滋病,,0,,,,,,,,,,,, 20 | 1018,人类进化,所有属性+1,,2,,1,1,1,1,1,,,,,, 21 | 1019,超凡,初始可用属性点+4,,2,4,,,,,,,,,,, 22 | 1020,父母美貌,颜值+2,,1,,,,2,,,,,,,, 23 | 1021,红颜薄命,颜值+2,体质-2,,0,,,,2,-2,,,,,,, 24 | 1022,属蛇,不会被蛇咬死,,0,,,,,,,,,,,, 25 | 1023,半神,所有属性+2,,3,,2,2,2,2,2,,,,,, 26 | 1024,人中龙凤,天生双重性别,,2,,,,,,,1003,1004,1025,,, 27 | 1025,阴阳之外,天生无性别,,2,,,,,,,1003,1024,1004,1113,, 28 | 1026,彩虹之下,可能和同性交往,,0,,,,,,,1113,,,,, 29 | 1027,斩情证道,终生不恋爱结婚,,1,,,,,,,1113,,,,, 30 | 1028,桃花连连,恋爱机会提升,,0,,,,,,,,,,,, 31 | 1029,平安童年,12岁前父母都健在,,1,,,,,,,,,,,, 32 | 1030,宠物大师,宠物不会意外死亡,,0,,,,,,,,,,,, 33 | 1031,天生残疾,体质-2,,0,,,,,-2,,,,,,, 34 | 1032,早产儿,所有属性-1,,0,,-1,-1,-1,-1,-1,,,,,, 35 | 1033,十死无生,体质-10,,0,,,,,-10,,,,,,, 36 | 1034,家运不顺,家境-2,,0,,,-2,,,,,,,,, 37 | 1035,头着地,智力-2,,0,,,,,,-2,,,,,, 38 | 1036,胎教,智力+1,,0,,,,,,1,,,,,, 39 | 1037,班中红人,和同学容易处好关系,,0,,,,,,,,,,,, 40 | 1038,骑士,能轻松学会骑车,,0,,,,,,,,,,,, 41 | 1039,永远的神,电竞天才,,1,,,,,,,,,,,, 42 | 1040,戒律,赌毒不沾,,0,,,,,,,,,,,, 43 | 1041,丁克,不生孩子,,1,,,,,,,1113,,,,, 44 | 1042,少数民族,高考+5分,,0,,,,,,,,,,,, 45 | 1043,老司机,你和家人不会发生车祸,,0,,,,,,,,,,,, 46 | 1044,低压,你的家人不会心脏病,,0,,,,,,,,,,,, 47 | 1045,战功,你退伍后会当官,,0,,,,,,,,,,,, 48 | 1046,不孕不育,你生不出孩子,,1,,,,,,,1113,,,,, 49 | 1047,白头偕老,爱人至少能活到70岁,,1,,,,,,,,,,,, 50 | 1048,神秘的小盒子,100岁时才能开启,,3,,,,,,,,,,,, 51 | 1049,三十而立,30岁时家境+2,AGE?[30],0,,,2,,,,,,,,, 52 | 1050,四十不惑,40岁时智力+2,AGE?[40],0,,,,,,2,,,,,, 53 | 1051,知天命,50岁时智力、快乐+1,AGE?[50],0,,1,,,,1,,,,,, 54 | 1052,耳顺,60岁时快乐+2,AGE?[60],0,,2,,,,,,,,,, 55 | 1053,从心所欲,70岁时快乐+3,AGE?[70],0,,3,,,,,,,,,, 56 | 1054,老当益壮,60岁时体质+2,AGE?[60],1,,,,,2,,,,,,, 57 | 1055,鹤发童颜,70岁时颜值+3,AGE?[70],0,,,,3,,,,,,,, 58 | 1056,学前启蒙,5岁时智力+2,AGE?[5],1,,,,,,2,,,,,, 59 | 1057,十八变,18岁时颜值+2,AGE?[18],1,,,,2,,,,,,,, 60 | 1058,迟来之财,90岁时家境+4,AGE?[90],0,,,4,,,,,,,,, 61 | 1059,理财达人,30、40、50岁时家境+1,"AGE?[30,40,50]",0,,,1,,,,,,,,, 62 | 1060,成熟,12、18岁时智力+1,"AGE?[12,18]",1,,,,,,1,,,,,, 63 | 1061,形象管理,16、24岁时颜值+1,"AGE?[16,24]",1,,,,1,,,,,,,, 64 | 1062,成年礼,18岁时快乐+1,AGE?[18],0,,1,,,,,,,,,, 65 | 1063,开光之胎,初始可用属性点+1,,0,1,,,,,,,,,,, 66 | 1064,天命,初始可用属性点+8,,3,8,,,,,,,,,,, 67 | 1065,祖传药丸,功能不明,,1,,,,,,,,,,,, 68 | 1066,精准扶贫,家境为0时家境+1,(MNY<1)&(MNY>-1),0,,,1,,,,,,,,, 69 | 1067,乐天派,快乐为0时快乐+1,(SPR<1)&(SPR>-1),1,,1,,,,,,,,,, 70 | 1068,命悬一线,体质为0时体质+1,(STR<1)&(STR>-1),0,,,,,1,,,,,,, 71 | 1069,智可生财,若20岁时智力>8,家境+2,(AGE?[20])&(INT>8),0,,,2,,,,,,,,, 72 | 1070,舔狗甚多,若20岁时颜值>8,快乐+2,(AGE?[20])&(CHR>8),0,,2,,,,,,,,,, 73 | 1071,保胎丸,你不会胎死腹中,,0,,,,,,,,,,,, 74 | 1072,白化病,你不会遭遇枪击,,0,,,,,,,,,,,, 75 | 1073,佛宗,考上哈佛大学的几率提高,,0,,,,,,,,,,,, 76 | 1074,悟道,智力>10时快乐+3,INT>10,1,,3,,,,,,,,,, 77 | 1075,驻颜,体质>10时颜值+3,STR>10,0,,,,3,,,,,,,, 78 | 1076,界限突破,体质>10时快乐+3,STR>10,1,,3,,,,,,,,,, 79 | 1077,倾城,颜值>10时快乐+3,CHR>10,1,,3,,,,,,,,,, 80 | 1078,训练有方,智力>10时体质+3,INT>10,0,,,,,3,,,,,,, 81 | 1079,相由心生,智力>10时颜值+3,INT>10,0,,,,3,,,,,,,, 82 | 1080,智多鑫,智力>10时家境+3,INT>10,0,,,3,,,,,,,,, 83 | 1081,灵光,快乐>10时其他属性+1,SPR>10,0,,,1,1,1,1,,,,,, 84 | 1082,天启,快乐>10时其他属性+2,SPR>10,1,,,2,2,2,2,,,,,, 85 | 1083,神谕,快乐>10时其他属性+3,SPR>10,2,,,3,3,3,3,,,,,, 86 | 1084,献祭,初始可用属性点-2,快乐+2,,0,-2,2,,,,,1122,,,,, 87 | 1085,幸运儿,初始可用属性点-3,快乐+5,,1,-3,5,,,,,1122,,,,, 88 | 1086,挑战者,初始可用点-10,,0,-10,,,,,,1122,,,,, 89 | 1087,你不懂,家境>10时快乐+3,MNY>10,1,,3,,,,,,,,,, 90 | 1088,整容,家境>10时颜值+3,MNY>10,0,,,,3,,,,,,,, 91 | 1089,钻石健身卡,家境>10时体质+3,MNY>10,0,,,,,3,,,,,,, 92 | 1090,身残志坚,体质<0时其他属性+1,STR<0,0,,1,1,1,,1,,,,,, 93 | 1091,活死人,体质<-1时其他属性+2,STR<0,1,,2,2,2,,2,,,,,, 94 | 1092,开一扇窗,颜值<0时其他属性+1,CHR<0,0,,1,1,,1,1,,,,,, 95 | 1093,大额头,颜值-2,智力+2,,0,,,,-2,,2,,,,,, 96 | 1094,痘痘脸,颜值-1,,0,,,,-1,,,,,,,, 97 | 1095,潜能,家境<0时其他属性+1,MNY<0,0,,1,,1,1,1,,,,,, 98 | 1096,哀兵,快乐<0时其他属性+1,SPR<0,0,,,1,1,1,1,,,,,, 99 | 1097,苦痛侍僧,快乐<-1时其他属性+2,SPR<-1,1,,,2,2,2,2,,,,,, 100 | 1098,觉醒,家境<-1时其他属性+2,MNY<-1,1,,2,,2,2,2,,,,,, 101 | 1099,抖M,家境-2,快乐+2,,0,,2,-2,,,,,,,,, 102 | 1100,海的女儿,颜值-2,初始可用属性点+1,,0,1,,,-2,,,,,,,, 103 | 1101,进阶,所有属性>5时,所有属性+1,(SPR>5)&(MNY>5)&(CHR>5)&(STR>5)&(INT>5),0,,1,1,1,1,1,,,,,, 104 | 1102,超进化,所有属性>5时,所有属性+2,(SPR>5)&(MNY>5)&(CHR>5)&(STR>5)&(INT>5),1,,2,2,2,2,2,,,,,, 105 | 1103,白色胶囊,你10岁时无事发生,AGE?[10],0,,,,,,,,,,,, 106 | 1104,紫色胶囊,跳过你的40~50岁,"AGE?[40,41,42,43,44,45,46,47,48,49,50]",2,,,,,,,,,,,, 107 | 1105,蓝色胶囊,你20、30岁时无事发生,"AGE?[20,30]",1,,,,,,,,,,,, 108 | 1106,健康饮食,你不吃洋快餐,,0,,,,,,,,,,,, 109 | 1107,不想罢了,你不会上清华大学,,0,,,,,,,,,,,, 110 | 1108,挑衅,你喜欢没事找事,,0,,,,,,,,,,,, 111 | 1109,旅行者,你喜欢旅游,,0,,,,,,,,,,,, 112 | 1110,水仙,你比较自恋,,0,,,,,,,,,,,, 113 | 1111,缺一门,无效果,,0,,,,,,,,,,,, 114 | 1112,异界来客,你可能听到一些绝密消息,,2,,,,,,,,,,,, 115 | 1113,三胎人生,你尽可能生三胎,,1,,,,,,,1003,1025,1026,1027,1041,1046 116 | 1114,橙色胶囊,跳过你的60~90岁,"AGE?[60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90]",3,,,,,,,,,,,, 117 | 1115,宙斯,参加奥赛的几率提高,,0,,,,,,,,,,,, 118 | 1116,为人民服务,考公务员时一定能考上,,0,,,,,,,,,,,, 119 | 1117,表现良好,入狱会减刑,,0,,,,,,,,,,,, 120 | 1118,小吉,运气稍微提升,,0,,,,,,,,,,,, 121 | 1119,天秤座,据说做事很公平,,0,,,,,,,,,,,, 122 | 1120,万里挑一,你很攻,,0,,,,,,,,,,,, 123 | 1121,把握不住,你有强迫症,,0,,,,,,,,,,,, 124 | 1122,急了急了,赶着投胎,不要初始属性了,,1,-20,,,,,,1084,1085,1086,,, 125 | 1123,不离不弃,你不会离婚,,0,,,,,,,,,,,, 126 | 1124,足量,身高不矮,,0,,,,,,,,,,,, 127 | 1125,易胖体质,颜值更容易降低,,0,,,,,,,,,,,, 128 | 1126,黄帝,种族主义者,,0,,,,,,,,,,,, 129 | 1127,左撇子,习惯使用左手,,0,,,,,,,,,,,, 130 | 1128,克苏鲁,&▓▓▓◆▓▓▓¥#▓@■.◆,,2,,,,,,,,,,,, 131 | 1129,不连续存在,你还拥有其他人格,,2,,,,,,,,,,,, 132 | 1130,占位符,少一个可选天赋,,0,,,,,,,,,,,, 133 | 1131,魔法棒,不知道有什么用……,,2,,,,,,,,,,,, 134 | -------------------------------------------------------------------------------- /view/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Life Restart 14 | 15 | 16 |
17 |
18 | 23 | 28 | 33 |
34 |
35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /view/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Life Restart 14 | 15 | 16 |
17 |
18 | 23 | 28 | 33 |
34 |
35 | 36 | 37 | 38 | 51 | 52 | -------------------------------------------------------------------------------- /view/condition_test.html: -------------------------------------------------------------------------------- 1 | 颜值: (CHR)
2 | 智力: (INT)
3 | 体质: (STR)
4 | 家境: (MNY)
5 | 快乐: (SPR)
6 | 生命: (LIF)
7 | 天赋: (TLT)
8 | 事件: (EVT)
9 |

10 | 表达式:

11 | 结果:

12 |
13 | 14 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /view/light.css: -------------------------------------------------------------------------------- 1 | @media (min-width:640px){html{font-size:24px;}} 2 | @media (min-width:631px) and (max-width:639px){html{font-size:23.66px;}} 3 | @media (min-width:622px) and (max-width:630px){html{font-size:23.33px;}} 4 | @media (min-width:613px) and (max-width:621px){html{font-size:23px;}} 5 | @media (min-width:604px) and (max-width:612px){html{font-size:22.66px;}} 6 | @media (min-width:595px) and (max-width:603px){html{font-size:22.33px;}} 7 | @media (min-width:586px) and (max-width:594px){html{font-size:22px;}} 8 | @media (min-width:577px) and (max-width:585px){html{font-size:21.66px;}} 9 | @media (min-width:568px) and (max-width:576px){html{font-size:21.33px;}} 10 | @media (min-width:559px) and (max-width:567px){html{font-size:21px;}} 11 | @media (min-width:550px) and (max-width:558px){html{font-size:20.66px;}} 12 | @media (min-width:541px) and (max-width:549px){html{font-size:20.33px;}} 13 | @media (min-width:533px) and (max-width:540px){html{font-size:20px;}} 14 | @media (min-width:524px) and (max-width:532px){html{font-size:19.66px;}} 15 | @media (min-width:515px) and (max-width:523px){html{font-size:19.33px;}} 16 | @media (min-width:506px) and (max-width:514px){html{font-size:19px;}} 17 | @media (min-width:497px) and (max-width:505px){html{font-size:18.66px;}} 18 | @media (min-width:488px) and (max-width:496px){html{font-size:18.33px;}} 19 | @media (min-width:480px) and (max-width:487px){html{font-size:18px;}} 20 | @media (min-width:471px) and (max-width:479px){html{font-size:17.66px;}} 21 | @media (min-width:462px) and (max-width:470px){html{font-size:17.33px;}} 22 | @media (min-width:453px) and (max-width:461px){html{font-size:17px;}} 23 | @media (min-width:444px) and (max-width:452px){html{font-size:17.12px;}} 24 | @media (min-width:435px) and (max-width:443px){html{font-size:16.33px;}} 25 | @media (min-width:426px) and (max-width:434px){html{font-size:16px;}} 26 | @media (min-width:417px) and (max-width:425px){html{font-size:15.66px;}} 27 | @media (min-width:408px) and (max-width:416px){html{font-size:15.33px;}} 28 | @media (min-width:400px) and (max-width:407px){html{font-size:15px;}} 29 | @media (min-width:391px) and (max-width:399px){html{font-size:14.66px;}} 30 | @media (min-width:382px) and (max-width:390px){html{font-size:14.33px;}} 31 | @media (min-width:374px) and (max-width:381px){html{font-size:14px;}} 32 | @media (min-width:365px) and (max-width:373px){html{font-size:13.66px;}} 33 | @media (min-width:356px) and (max-width:364px){html{font-size:13.33px;}} 34 | @media (min-width:347px) and (max-width:355px){html{font-size:13px;}} 35 | @media (min-width:338px) and (max-width:346px){html{font-size:12.66px;}} 36 | @media (min-width:329px) and (max-width:337px){html{font-size:12.44px;}} 37 | @media (max-width:328px){html{font-size:12px;}} 38 | 39 | @font-face { 40 | font-family: 'iconfont'; 41 | src: url('iconfont.woff2?t=1628944689555') format('woff2'), 42 | url('iconfont.woff?t=1628944689555') format('woff'), 43 | url('iconfont.ttf?t=1628944689555') format('truetype'); 44 | } 45 | 46 | html { 47 | font-family: PingFangSC, 'Noto Sans CJK SC', 'MS Yahei'; 48 | } 49 | 50 | body { 51 | user-select: none; 52 | } 53 | 54 | #main { 55 | align-content: center; 56 | width: 100%; 57 | height: 100%; 58 | position: relative; 59 | } 60 | 61 | #title { 62 | position: fixed; 63 | font-size: 3rem; 64 | font-weight: 700; 65 | top: 35%; 66 | left: 50%; 67 | white-space: nowrap; 68 | transform: translate(-50%,-50%); 69 | text-align: center; 70 | } 71 | 72 | .mainbtn { 73 | position: fixed; 74 | top: 65%; 75 | left: 50%; 76 | padding: 0.5rem 1.5rem; 77 | border: 1px #ccc solid; 78 | border-radius: 0.2rem; 79 | background-color:white; 80 | font-size: 1.6rem; 81 | white-space: nowrap; 82 | transform: translate(-50%,-50%); 83 | cursor: pointer; 84 | z-index:2; 85 | } 86 | 87 | .mainbtn:hover { 88 | background: #ff7878; 89 | color: #fff; 90 | transition: all .4s ease 0s; 91 | } 92 | 93 | .iconfont { 94 | font-family: "iconfont" !important; 95 | font-style: normal; 96 | -webkit-font-smoothing: antialiased; 97 | -moz-osx-font-smoothing: grayscale; 98 | } 99 | 100 | #rank { 101 | position: fixed; 102 | top: 1rem; 103 | right: 1rem; 104 | padding: 0.1rem 1rem; 105 | border: none; 106 | border-radius: 0.2rem; 107 | background-color:lightsteelblue; 108 | font-size: 1.4rem; 109 | color: white; 110 | cursor: pointer; 111 | z-index:2; 112 | } 113 | 114 | #themeToggleBtn { 115 | position: fixed; 116 | right: 1rem; 117 | bottom: 1rem; 118 | padding: 0.1rem 1rem; 119 | border: none; 120 | border-radius: 0.2rem; 121 | background-color:#222831; 122 | font-size: 1.4rem; 123 | color: #EEEEEE; 124 | cursor: pointer; 125 | z-index:2; 126 | } 127 | 128 | .head { 129 | position: fixed; 130 | font-size: 1.4rem; 131 | top: 1.1rem; 132 | left: 50%; 133 | white-space: nowrap; 134 | transform: translateX(-50%); 135 | text-align: center; 136 | } 137 | 138 | .judge, 139 | .lifeTrajectory, 140 | .propinitial, 141 | .selectlist { 142 | position: fixed; 143 | list-style-type: none; 144 | left: 50%; 145 | top: 5rem; 146 | bottom: 8.5rem; 147 | width: 30rem; 148 | max-width: calc(100% - 2rem); 149 | margin: auto; 150 | padding: 0; 151 | overflow: auto; 152 | transform: translateX(-50%); 153 | text-align: center; 154 | } 155 | 156 | .lifeProperty{ 157 | position: fixed; 158 | list-style-type: none; 159 | left: 50%; 160 | top: 2rem; 161 | width: 30rem; 162 | max-width: calc(100% - 2rem); 163 | padding: 0; 164 | overflow: auto; 165 | transform: translateX(-50%); 166 | display: flex; 167 | color: black; 168 | } 169 | 170 | .lifeProperty > li { 171 | width: 100%; 172 | position: relative; 173 | border: 1px #ccc solid; 174 | display: inline-block; 175 | margin: 0.1rem 2px; 176 | font-size: 1rem; 177 | text-align: center; 178 | border-radius: 0.2rem; 179 | } 180 | 181 | .selectlist > li { 182 | position: relative; 183 | border: 1px #ccc solid; 184 | display: inline-block; 185 | width: 95%; 186 | margin: 0.1rem auto; 187 | font-size: 1.4rem; 188 | text-align: center; 189 | border-radius: 0.2rem; 190 | cursor: pointer; 191 | } 192 | 193 | .grade0b { 194 | background-color: #ededed; 195 | border: #c5c5c5 2px solid !important; 196 | } 197 | 198 | .grade1b { 199 | background-color: #7ea5ec; 200 | border: #c5c5c5 2px solid !important; 201 | } 202 | 203 | .grade2b { 204 | background-color: #e2a7ff; 205 | border: #c5c5c5 2px solid !important; 206 | } 207 | 208 | .grade3b { 209 | background-color: #ffa07a; 210 | border: #c5c5c5 2px solid !important; 211 | } 212 | 213 | @media (min-width:1080px) { 214 | .grade0b:hover { 215 | background-color: #868686; 216 | transition: all .3s ease 0s; 217 | } 218 | 219 | .grade1b:hover { 220 | background-color: #5d90ff; 221 | transition: all .3s ease 0s; 222 | } 223 | 224 | .grade2b:hover { 225 | background-color: #bc72ec; 226 | transition: all .3s ease 0s; 227 | } 228 | 229 | .grade3b:hover { 230 | background-color: #e09074; 231 | transition: all .3s ease 0s; 232 | } 233 | } 234 | .grade0b.selected { 235 | background-color: #444; 236 | box-shadow: #bbb 0px 0px 10px; 237 | color: #fff; 238 | } 239 | 240 | .grade1b.selected { 241 | background-color: #407dec; 242 | box-shadow: #bbb 0px 0px 10px; 243 | color: #fff; 244 | } 245 | 246 | .grade2b.selected { 247 | background-color: #b362e7; 248 | box-shadow: #bbb 0px 0px 10px; 249 | color: #fff; 250 | } 251 | 252 | .grade3b.selected { 253 | background-color: #ff7f4d; 254 | box-shadow: #bbb 0px 0px 10px; 255 | color: #fff; 256 | } 257 | 258 | .judge > li.grade1 span, 259 | .judge > li.grade1{ 260 | background-color: #7ea5ec; 261 | } 262 | .judge > li.grade2 span, 263 | .judge > li.grade2{ 264 | background-color: #e2a7ff; 265 | } 266 | .judge > li.grade3 span, 267 | .judge > li.grade3{ 268 | background-color: #ffa07a; 269 | } 270 | 271 | .propinitial { 272 | top: 5rem; 273 | bottom: 17rem; 274 | } 275 | 276 | .propinitial > li { 277 | position: relative; 278 | display: inline-block; 279 | width: 95%; 280 | margin: 0.1rem auto; 281 | font-size: 1.4rem; 282 | text-align: center; 283 | border-radius: 0.2rem; 284 | padding: 0.2rem; 285 | } 286 | 287 | .propinitial > li > input { 288 | height: 2.2rem; 289 | width: 2.2rem; 290 | margin: 0 0.5rem; 291 | padding: 0; 292 | text-align: center; 293 | font-size: 2rem; 294 | border: 0.1rem #ccc solid; 295 | } 296 | 297 | .propbtn { 298 | position: relative; 299 | cursor: pointer; 300 | font-size: 2rem; 301 | user-select: none; 302 | } 303 | 304 | .propbtn:hover{ 305 | color: #5c5c5c; 306 | transition: all .2s ease 0s; 307 | } 308 | 309 | .lifeTrajectory { 310 | border: 1px #a7a7a7 solid; 311 | border-radius: 10px; 312 | background-color: #ffffff; 313 | } 314 | 315 | .judge > li, 316 | .lifeTrajectory > li { 317 | position: relative; 318 | width: calc(100% - 7rem); 319 | margin: 0.5rem 0; 320 | padding: 0.5rem 1rem 0.5rem 6rem; 321 | font-size: 1.4rem; 322 | background-color: #fff; 323 | box-shadow: #a7a7a7 0 0 0.4rem; 324 | user-select: none; 325 | } 326 | 327 | .judge > li > span, 328 | .lifeTrajectory > li > span { 329 | position: absolute; 330 | left: 0; 331 | width: 6rem; 332 | text-align: right; 333 | } 334 | 335 | .judge > li { 336 | box-shadow: lightgray 0 0 0.4rem; 337 | width: calc(100% - 9rem); 338 | margin: 0.5rem; 339 | padding: 0.5rem 1rem 0.5rem 7rem; 340 | border-radius: 10px; 341 | } 342 | 343 | .judge > li > span { 344 | height: calc(100% - 1rem); 345 | padding: 0.5rem 0; 346 | top: 0; 347 | border-radius: 10px; 348 | } 349 | 350 | 351 | @import url("https://fonts.googleapis.com/css?family=Montserrat:400,400i,700"); 352 | :root { 353 | font-family: "Montserrat"; 354 | } 355 | 356 | html, 357 | body { 358 | margin: 0; 359 | height: 100%; 360 | } 361 | 362 | body { 363 | display: flex; 364 | align-items: center; 365 | justify-content: center; 366 | } 367 | 368 | i { 369 | color: inherit; 370 | } 371 | 372 | .banners-container { 373 | position: fixed; 374 | top: 0; 375 | left: 0; 376 | width: 100%; 377 | z-index: 9; 378 | } 379 | 380 | .banner { 381 | color: white; 382 | font-weight: 700; 383 | padding: 2rem; 384 | display: flex; 385 | flex-direction: row; 386 | align-items: center; 387 | } 388 | .banner .banner-message { 389 | flex: 1; 390 | padding: 0 2rem; 391 | word-break: break-word; 392 | overflow: auto; 393 | } 394 | .banner .banner-close { 395 | display: flex; 396 | align-items: center; 397 | justify-content: center; 398 | padding: 0.1rem; 399 | border-radius: 4px; 400 | cursor: pointer; 401 | transition: background 0.3s; 402 | } 403 | 404 | .banner .iconfont { 405 | font-size: 2rem; 406 | } 407 | 408 | .banner .banner-close:hover { 409 | background: rgba(0, 0, 0, 0.12); 410 | } 411 | 412 | .banner.success { 413 | background: lightgreen; 414 | } 415 | .banner.success::after { 416 | background: lightgreen; 417 | } 418 | .banner.error { 419 | background: #ed1c24; 420 | } 421 | .banner.error::after { 422 | background: #ed1c24; 423 | } 424 | .banner.info { 425 | background: skyblue; 426 | } 427 | .banner.info::after { 428 | background: skyblue; 429 | } 430 | 431 | .banner::after { 432 | content: ""; 433 | position: absolute; 434 | height: 10%; 435 | width: 100%; 436 | bottom: 100%; 437 | left: 0; 438 | } 439 | 440 | .banner:not(.visible) { 441 | display: none; 442 | transform: translateY(-100%); 443 | } 444 | 445 | .banner.visible { 446 | box-shadow: 0 2px 2px 2px rgba(0, 0, 0, 0.12); 447 | animation-name: banner-in; 448 | animation-direction: forwards; 449 | animation-duration: 0.6s; 450 | animation-timing-function: ease-in-out; 451 | animation-fill-mode: forwards; 452 | animation-iteration-count: 1; 453 | } 454 | 455 | @keyframes banner-in { 456 | 0% { 457 | transform: translateY(-100%); 458 | } 459 | 50% { 460 | transform: translateY(10%); 461 | } 462 | 100% { 463 | transform: translateY(0); 464 | } 465 | } 466 | .show-banner { 467 | appearance: none; 468 | background: #ededed; 469 | border: 0; 470 | padding: 1rem 2rem; 471 | border-radius: 4px; 472 | cursor: pointer; 473 | text-transform: uppercase; 474 | margin: 0.25rem; 475 | } 476 | 477 | /** 478 | * @license 479 | * Copyright Akveo. All Rights Reserved. 480 | * Licensed under the MIT License. See License.txt in the project root for license information. 481 | */ 482 | .eva-animation { 483 | animation-duration: 1s; 484 | animation-fill-mode: both; } 485 | 486 | .eva-infinite { 487 | animation-iteration-count: infinite; } 488 | 489 | .eva-icon-shake { 490 | animation-name: eva-shake; } 491 | 492 | .eva-icon-zoom { 493 | animation-name: eva-zoomIn; } 494 | 495 | .eva-icon-pulse { 496 | animation-name: eva-pulse; } 497 | 498 | .eva-icon-flip { 499 | animation-name: eva-flipInY; } 500 | 501 | .eva-hover { 502 | display: inline-block; } 503 | 504 | .eva-hover:hover .eva-icon-hover-shake, .eva-parent-hover:hover .eva-icon-hover-shake { 505 | animation-name: eva-shake; } 506 | 507 | .eva-hover:hover .eva-icon-hover-zoom, .eva-parent-hover:hover .eva-icon-hover-zoom { 508 | animation-name: eva-zoomIn; } 509 | 510 | .eva-hover:hover .eva-icon-hover-pulse, .eva-parent-hover:hover .eva-icon-hover-pulse { 511 | animation-name: eva-pulse; } 512 | 513 | .eva-hover:hover .eva-icon-hover-flip, .eva-parent-hover:hover .eva-icon-hover-flip { 514 | animation-name: eva-flipInY; } 515 | 516 | @keyframes eva-flipInY { 517 | from { 518 | transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 519 | animation-timing-function: ease-in; 520 | opacity: 0; } 521 | 40% { 522 | transform: perspective(400px) rotate3d(0, 1, 0, -20deg); 523 | animation-timing-function: ease-in; } 524 | 60% { 525 | transform: perspective(400px) rotate3d(0, 1, 0, 10deg); 526 | opacity: 1; } 527 | 80% { 528 | transform: perspective(400px) rotate3d(0, 1, 0, -5deg); } 529 | to { 530 | transform: perspective(400px); } } 531 | 532 | @keyframes eva-shake { 533 | from, 534 | to { 535 | transform: translate3d(0, 0, 0); } 536 | 10%, 537 | 30%, 538 | 50%, 539 | 70%, 540 | 90% { 541 | transform: translate3d(-3px, 0, 0); } 542 | 20%, 543 | 40%, 544 | 60%, 545 | 80% { 546 | transform: translate3d(3px, 0, 0); } } 547 | 548 | @keyframes eva-pulse { 549 | from { 550 | transform: scale3d(1, 1, 1); } 551 | 50% { 552 | transform: scale3d(1.2, 1.2, 1.2); } 553 | to { 554 | transform: scale3d(1, 1, 1); } } 555 | 556 | @keyframes eva-zoomIn { 557 | from { 558 | opacity: 1; 559 | transform: scale3d(0.5, 0.5, 0.5); } 560 | 50% { 561 | opacity: 1; } } 562 | 563 | ::-webkit-scrollbar { 564 | width: 0 !important 565 | } -------------------------------------------------------------------------------- /view/dark.css: -------------------------------------------------------------------------------- 1 | @media (min-width:640px){html{font-size:24px;}} 2 | @media (min-width:631px) and (max-width:639px){html{font-size:23.66px;}} 3 | @media (min-width:622px) and (max-width:630px){html{font-size:23.33px;}} 4 | @media (min-width:613px) and (max-width:621px){html{font-size:23px;}} 5 | @media (min-width:604px) and (max-width:612px){html{font-size:22.66px;}} 6 | @media (min-width:595px) and (max-width:603px){html{font-size:22.33px;}} 7 | @media (min-width:586px) and (max-width:594px){html{font-size:22px;}} 8 | @media (min-width:577px) and (max-width:585px){html{font-size:21.66px;}} 9 | @media (min-width:568px) and (max-width:576px){html{font-size:21.33px;}} 10 | @media (min-width:559px) and (max-width:567px){html{font-size:21px;}} 11 | @media (min-width:550px) and (max-width:558px){html{font-size:20.66px;}} 12 | @media (min-width:541px) and (max-width:549px){html{font-size:20.33px;}} 13 | @media (min-width:533px) and (max-width:540px){html{font-size:20px;}} 14 | @media (min-width:524px) and (max-width:532px){html{font-size:19.66px;}} 15 | @media (min-width:515px) and (max-width:523px){html{font-size:19.33px;}} 16 | @media (min-width:506px) and (max-width:514px){html{font-size:19px;}} 17 | @media (min-width:497px) and (max-width:505px){html{font-size:18.66px;}} 18 | @media (min-width:488px) and (max-width:496px){html{font-size:18.33px;}} 19 | @media (min-width:480px) and (max-width:487px){html{font-size:18px;}} 20 | @media (min-width:471px) and (max-width:479px){html{font-size:17.66px;}} 21 | @media (min-width:462px) and (max-width:470px){html{font-size:17.33px;}} 22 | @media (min-width:453px) and (max-width:461px){html{font-size:17px;}} 23 | @media (min-width:444px) and (max-width:452px){html{font-size:17.12px;}} 24 | @media (min-width:435px) and (max-width:443px){html{font-size:16.33px;}} 25 | @media (min-width:426px) and (max-width:434px){html{font-size:16px;}} 26 | @media (min-width:417px) and (max-width:425px){html{font-size:15.66px;}} 27 | @media (min-width:408px) and (max-width:416px){html{font-size:15.33px;}} 28 | @media (min-width:400px) and (max-width:407px){html{font-size:15px;}} 29 | @media (min-width:391px) and (max-width:399px){html{font-size:14.66px;}} 30 | @media (min-width:382px) and (max-width:390px){html{font-size:14.33px;}} 31 | @media (min-width:374px) and (max-width:381px){html{font-size:14px;}} 32 | @media (min-width:365px) and (max-width:373px){html{font-size:13.66px;}} 33 | @media (min-width:356px) and (max-width:364px){html{font-size:13.33px;}} 34 | @media (min-width:347px) and (max-width:355px){html{font-size:13px;}} 35 | @media (min-width:338px) and (max-width:346px){html{font-size:12.66px;}} 36 | @media (min-width:329px) and (max-width:337px){html{font-size:12.44px;}} 37 | @media (max-width:328px){html{font-size:12px;}} 38 | 39 | @font-face { 40 | font-family: 'iconfont'; 41 | src: url('iconfont.woff2?t=1628944689555') format('woff2'), 42 | url('iconfont.woff?t=1628944689555') format('woff'), 43 | url('iconfont.ttf?t=1628944689555') format('truetype'); 44 | } 45 | 46 | html { 47 | background-color: #222831; 48 | font-family: PingFangSC, 'Noto Sans CJK SC', 'MS Yahei'; 49 | } 50 | 51 | body { 52 | user-select: none; 53 | } 54 | 55 | #main { 56 | align-content: center; 57 | width: 100%; 58 | height: 100%; 59 | position: relative; 60 | } 61 | 62 | #title { 63 | position: fixed; 64 | font-size: 3rem; 65 | font-weight: 700; 66 | top: 35%; 67 | left: 50%; 68 | white-space: nowrap; 69 | transform: translate(-50%,-50%); 70 | text-align: center; 71 | color: #EEEEEE; 72 | } 73 | 74 | .mainbtn { 75 | position: fixed; 76 | top: 65%; 77 | left: 50%; 78 | padding: 0.5rem 1.5rem; 79 | border: 1px #EEEEEE solid; 80 | border-radius: 0.2rem; 81 | background-color:#393E46; 82 | font-size: 1.6rem; 83 | white-space: nowrap; 84 | transform: translate(-50%,-50%); 85 | cursor: pointer; 86 | z-index:2; 87 | color: #EEEEEE; 88 | } 89 | 90 | .mainbtn:hover { 91 | background: #ff7878; 92 | color: #fff; 93 | transition: all .4s ease 0s; 94 | } 95 | 96 | .iconfont { 97 | font-family: "iconfont" !important; 98 | font-style: normal; 99 | -webkit-font-smoothing: antialiased; 100 | -moz-osx-font-smoothing: grayscale; 101 | } 102 | 103 | #rank { 104 | position: fixed; 105 | top: 1rem; 106 | right: 1rem; 107 | padding: 0.1rem 1rem; 108 | border: none; 109 | border-radius: 0.2rem; 110 | background-color:lightsteelblue; 111 | font-size: 1.4rem; 112 | color: #EEEEEE; 113 | cursor: pointer; 114 | z-index:2; 115 | } 116 | 117 | #themeToggleBtn { 118 | position: fixed; 119 | right: 1rem; 120 | bottom: 1rem; 121 | padding: 0.1rem 1rem; 122 | border: none; 123 | border-radius: 0.2rem; 124 | background-color:#EEEEEE; 125 | font-size: 1.4rem; 126 | color: #222831; 127 | cursor: pointer; 128 | z-index:2; 129 | } 130 | 131 | .head { 132 | position: fixed; 133 | font-size: 1.4rem; 134 | top: 1.1rem; 135 | left: 50%; 136 | white-space: nowrap; 137 | transform: translateX(-50%); 138 | text-align: center; 139 | color: #EEEEEE; 140 | } 141 | 142 | .judge, 143 | .lifeTrajectory, 144 | .propinitial, 145 | .selectlist { 146 | position: fixed; 147 | list-style-type: none; 148 | left: 50%; 149 | top: 5rem; 150 | bottom: 8.5rem; 151 | width: 30rem; 152 | max-width: calc(100% - 2rem); 153 | margin: auto; 154 | padding: 0; 155 | overflow: auto; 156 | transform: translateX(-50%); 157 | text-align: center; 158 | } 159 | 160 | .lifeProperty{ 161 | position: fixed; 162 | list-style-type: none; 163 | left: 50%; 164 | top: 2rem; 165 | width: 30rem; 166 | max-width: calc(100% - 2rem); 167 | padding: 0; 168 | overflow: auto; 169 | transform: translateX(-50%); 170 | display: flex; 171 | color: white; 172 | } 173 | 174 | .lifeProperty > li { 175 | width: 100%; 176 | position: relative; 177 | border: 1px #ccc solid; 178 | display: inline-block; 179 | margin: 0.1rem 2px; 180 | font-size: 1rem; 181 | text-align: center; 182 | border-radius: 0.2rem; 183 | } 184 | 185 | .selectlist > li { 186 | position: relative; 187 | border: 1px #EEEEEE solid; 188 | display: inline-block; 189 | width: 95%; 190 | margin: 0.1rem auto; 191 | font-size: 1.4rem; 192 | text-align: center; 193 | border-radius: 0.2rem; 194 | cursor: pointer; 195 | color: #EEEEEE; 196 | user-select: none; 197 | } 198 | 199 | .grade0b { 200 | background-color: #464646; 201 | border: #f8f8f8 2px solid !important; 202 | } 203 | .grade1b { 204 | background-color: #6495ed; 205 | border: #f8f8f8 2px solid !important; 206 | } 207 | .grade2b { 208 | background-color: #e2a7ff; 209 | border: #f8f8f8 2px solid !important; 210 | } 211 | .grade3b { 212 | background-color: #ffa07a; 213 | border: #f8f8f8 2px solid !important; 214 | } 215 | 216 | @media (min-width:1080px) { 217 | .grade0b:hover { 218 | background-color: #c0c0c0; 219 | color: #3b3b3b; 220 | transition: all .3s ease 0s; 221 | } 222 | .grade1b:hover { 223 | background-color: #87cefa; 224 | color: #3b3b3b; 225 | transition: all .3s ease 0s; 226 | } 227 | .grade2b:hover { 228 | background-color: #e7beff; 229 | color: #3b3b3b; 230 | transition: all .3s ease 0s; 231 | } 232 | .grade3b:hover { 233 | background-color: #f7a989; 234 | color: #3b3b3b; 235 | transition: all .3s ease 0s; 236 | } 237 | } 238 | .grade0b.selected { 239 | background-color: #c0c0c0 !important; 240 | box-shadow: #ccc 0px 0px 10px; 241 | color: #3b3b3b; 242 | } 243 | 244 | .grade1b.selected { 245 | background-color: #87cefa !important; 246 | box-shadow: #ccc 0px 0px 10px; 247 | color: #3b3b3b; 248 | } 249 | 250 | .grade2b.selected { 251 | background-color: #e7beff !important; 252 | box-shadow: #ccc 0px 0px 10px; 253 | color: #3b3b3b; 254 | } 255 | 256 | .grade3b.selected { 257 | background-color: #f1bfac !important; 258 | box-shadow: #ccc 0px 0px 10px; 259 | color: #3b3b3b; 260 | } 261 | 262 | .judge > li.grade1 span, 263 | .judge > li.grade1{ 264 | background-color: #87cefa; 265 | } 266 | .judge > li.grade2 span, 267 | .judge > li.grade2{ 268 | background-color: #e7beff; 269 | } 270 | .judge > li.grade3 span, 271 | .judge > li.grade3{ 272 | background-color: #f7a989; 273 | } 274 | 275 | .propinitial { 276 | top: 5rem; 277 | bottom: 17rem; 278 | } 279 | 280 | .propbtn:hover{ 281 | color: #5c5c5c; 282 | transition: all .2s ease 0s; 283 | } 284 | 285 | .propinitial > li { 286 | position: relative; 287 | display: inline-block; 288 | width: 95%; 289 | margin: 0.1rem auto; 290 | font-size: 1.4rem; 291 | text-align: center; 292 | border-radius: 0.2rem; 293 | padding: 0.2rem; 294 | color: #EEEEEE; 295 | } 296 | 297 | .propinitial > li > input { 298 | height: 2.2rem; 299 | width: 2.2rem; 300 | margin: 0 0.5rem; 301 | padding: 0; 302 | text-align: center; 303 | font-size: 2rem; 304 | border: 0.1rem #EEEEEE solid; 305 | background-color: #393E46; 306 | color: #EEEEEE; 307 | } 308 | 309 | .propbtn { 310 | position: relative; 311 | cursor: pointer; 312 | font-size: 2rem; 313 | color: #EEEEEE; 314 | } 315 | 316 | .lifeTrajectory { 317 | border: 1px #9b9b9b solid; 318 | background-color: #393E46; 319 | border-radius: 10px; 320 | } 321 | 322 | .judge > li, 323 | .lifeTrajectory > li { 324 | position: relative; 325 | width: calc(100% - 7rem); 326 | margin: 0.5rem 0; 327 | padding: 0.5rem 1rem 0.5rem 6rem; 328 | font-size: 1.4rem; 329 | background-color: #4a5361; 330 | box-shadow: #EEEEEE 0 0 0.4rem; 331 | color: #EEEEEE; 332 | } 333 | 334 | .judge > li > span, 335 | .lifeTrajectory > li > span { 336 | position: absolute; 337 | left: 0; 338 | width: 6rem; 339 | text-align: right; 340 | } 341 | 342 | .judge > li { 343 | box-shadow: #EEEEEE 0 0 0.4rem; 344 | width: calc(100% - 9rem); 345 | margin: 0.5rem; 346 | padding: 0.5rem 1rem 0.5rem 7rem; 347 | border-radius: 10px; 348 | } 349 | 350 | .judge > li > span { 351 | height: calc(100% - 1rem); 352 | padding: 0.5rem 0; 353 | top: 0; 354 | border-radius: 10px; 355 | } 356 | 357 | 358 | @import url("https://fonts.googleapis.com/css?family=Montserrat:400,400i,700"); 359 | :root { 360 | font-family: "Montserrat"; 361 | } 362 | 363 | html, 364 | body { 365 | margin: 0; 366 | height: 100%; 367 | } 368 | 369 | body { 370 | display: flex; 371 | align-items: center; 372 | justify-content: center; 373 | } 374 | 375 | i { 376 | color: inherit; 377 | } 378 | 379 | .banners-container { 380 | position: fixed; 381 | top: 0; 382 | left: 0; 383 | width: 100%; 384 | z-index: 9; 385 | } 386 | 387 | .banner { 388 | color: white; 389 | font-weight: 700; 390 | padding: 2rem; 391 | display: flex; 392 | flex-direction: row; 393 | align-items: center; 394 | } 395 | .banner .banner-message { 396 | flex: 1; 397 | padding: 0 2rem; 398 | word-break: break-word; 399 | overflow: auto; 400 | } 401 | .banner .banner-close { 402 | display: flex; 403 | align-items: center; 404 | justify-content: center; 405 | padding: 0.1rem; 406 | border-radius: 4px; 407 | cursor: pointer; 408 | transition: background 0.3s; 409 | } 410 | 411 | .banner .iconfont { 412 | font-size: 2rem; 413 | } 414 | 415 | .banner .banner-close:hover { 416 | background: rgba(0, 0, 0, 0.12); 417 | } 418 | 419 | .banner.success { 420 | background: lightgreen; 421 | } 422 | .banner.success::after { 423 | background: lightgreen; 424 | } 425 | .banner.error { 426 | background: #ed1c24; 427 | } 428 | .banner.error::after { 429 | background: #ed1c24; 430 | } 431 | .banner.info { 432 | background: skyblue; 433 | } 434 | .banner.info::after { 435 | background: skyblue; 436 | } 437 | 438 | .banner::after { 439 | content: ""; 440 | position: absolute; 441 | height: 10%; 442 | width: 100%; 443 | bottom: 100%; 444 | left: 0; 445 | } 446 | 447 | .banner:not(.visible) { 448 | display: none; 449 | transform: translateY(-100%); 450 | } 451 | 452 | .banner.visible { 453 | box-shadow: 0 2px 2px 2px rgba(0, 0, 0, 0.12); 454 | animation-name: banner-in; 455 | animation-direction: forwards; 456 | animation-duration: 0.6s; 457 | animation-timing-function: ease-in-out; 458 | animation-fill-mode: forwards; 459 | animation-iteration-count: 1; 460 | } 461 | 462 | @keyframes banner-in { 463 | 0% { 464 | transform: translateY(-100%); 465 | } 466 | 50% { 467 | transform: translateY(10%); 468 | } 469 | 100% { 470 | transform: translateY(0); 471 | } 472 | } 473 | .show-banner { 474 | appearance: none; 475 | background: #ededed; 476 | border: 0; 477 | padding: 1rem 2rem; 478 | border-radius: 4px; 479 | cursor: pointer; 480 | text-transform: uppercase; 481 | margin: 0.25rem; 482 | } 483 | 484 | /** 485 | * @license 486 | * Copyright Akveo. All Rights Reserved. 487 | * Licensed under the MIT License. See License.txt in the project root for license information. 488 | */ 489 | .eva-animation { 490 | animation-duration: 1s; 491 | animation-fill-mode: both; } 492 | 493 | .eva-infinite { 494 | animation-iteration-count: infinite; } 495 | 496 | .eva-icon-shake { 497 | animation-name: eva-shake; } 498 | 499 | .eva-icon-zoom { 500 | animation-name: eva-zoomIn; } 501 | 502 | .eva-icon-pulse { 503 | animation-name: eva-pulse; } 504 | 505 | .eva-icon-flip { 506 | animation-name: eva-flipInY; } 507 | 508 | .eva-hover { 509 | display: inline-block; } 510 | 511 | .eva-hover:hover .eva-icon-hover-shake, .eva-parent-hover:hover .eva-icon-hover-shake { 512 | animation-name: eva-shake; } 513 | 514 | .eva-hover:hover .eva-icon-hover-zoom, .eva-parent-hover:hover .eva-icon-hover-zoom { 515 | animation-name: eva-zoomIn; } 516 | 517 | .eva-hover:hover .eva-icon-hover-pulse, .eva-parent-hover:hover .eva-icon-hover-pulse { 518 | animation-name: eva-pulse; } 519 | 520 | .eva-hover:hover .eva-icon-hover-flip, .eva-parent-hover:hover .eva-icon-hover-flip { 521 | animation-name: eva-flipInY; } 522 | 523 | @keyframes eva-flipInY { 524 | from { 525 | transform: perspective(400px) rotate3d(0, 1, 0, 90deg); 526 | animation-timing-function: ease-in; 527 | opacity: 0; } 528 | 40% { 529 | transform: perspective(400px) rotate3d(0, 1, 0, -20deg); 530 | animation-timing-function: ease-in; } 531 | 60% { 532 | transform: perspective(400px) rotate3d(0, 1, 0, 10deg); 533 | opacity: 1; } 534 | 80% { 535 | transform: perspective(400px) rotate3d(0, 1, 0, -5deg); } 536 | to { 537 | transform: perspective(400px); } } 538 | 539 | @keyframes eva-shake { 540 | from, 541 | to { 542 | transform: translate3d(0, 0, 0); } 543 | 10%, 544 | 30%, 545 | 50%, 546 | 70%, 547 | 90% { 548 | transform: translate3d(-3px, 0, 0); } 549 | 20%, 550 | 40%, 551 | 60%, 552 | 80% { 553 | transform: translate3d(3px, 0, 0); } } 554 | 555 | @keyframes eva-pulse { 556 | from { 557 | transform: scale3d(1, 1, 1); } 558 | 50% { 559 | transform: scale3d(1.2, 1.2, 1.2); } 560 | to { 561 | transform: scale3d(1, 1, 1); } } 562 | 563 | @keyframes eva-zoomIn { 564 | from { 565 | opacity: 1; 566 | transform: scale3d(0.5, 0.5, 0.5); } 567 | 50% { 568 | opacity: 1; } } 569 | 570 | ::-webkit-scrollbar { 571 | width: 0 !important 572 | } -------------------------------------------------------------------------------- /repl/app.js: -------------------------------------------------------------------------------- 1 | import { max, sum } from '../src/functions/util.js'; 2 | import { summary } from '../src/functions/summary.js' 3 | import { readFile } from 'fs/promises'; 4 | import Life from '../src/life.js'; 5 | 6 | global.json = async fileName => JSON.parse(await readFile(`data/${fileName}.json`)); 7 | 8 | class App { 9 | constructor() { 10 | this.#life = new Life(); 11 | } 12 | 13 | Steps= { 14 | TALENT: 'talent', 15 | PROPERTY: 'property', 16 | TRAJECTORY: 'trajectory', 17 | SUMMARY: 'summary', 18 | }; 19 | 20 | #step = this.Steps.SUMMARY; 21 | #life; 22 | #talentSelected = new Set(); 23 | #talentExtend = new Set(); 24 | #input; 25 | #auto; 26 | #isEnd; 27 | #propertyAllocation; 28 | #output; 29 | #exit; 30 | #interval; 31 | #style = { 32 | warn: ['\x1B[93m', '\x1B[39m'], // Bright Yellow 33 | grade1: ['\x1B[94m', '\x1B[39m'], // Bright Blue 34 | grade2: ['\x1B[95m', '\x1B[39m'], // Bright Magenta 35 | grade3: ['\x1B[93m', '\x1B[39m'], // Bright Yellow 36 | grade1b: ['\x1B[104m', '\x1B[49m'], // Bright Blue BG 37 | grade2b: ['\x1B[105m', '\x1B[49m'], // Bright Magenta BG 38 | grade3b: ['\x1B[103m', '\x1B[49m'], // Bright Yellow BG 39 | }; 40 | #randomTalents; 41 | 42 | style(type, str) { 43 | const style = this.#style[type]; 44 | if(!style) return str; 45 | return `${style[0]}${str}${style[1]}`; 46 | } 47 | 48 | async initial() { 49 | this.output('Now Loading...'); 50 | this.#talentExtend = global.localStorage.talentExtend; 51 | await this.#life.initial(); 52 | this.output(`\rLoading Complete. 53 | 人生重开模拟器 54 | 这垃圾人生一秒也不想待了 55 | \n🎉键入 \x1B[4m/remake\x1B[24m 开始游戏`, 56 | true 57 | ); 58 | } 59 | 60 | io(input, output, exit) { 61 | this.#input = input; 62 | this.#output = output; 63 | this.#exit = exit; 64 | input(command=>{ 65 | const ret = this.repl(command); 66 | if(!ret) return; 67 | if(typeof ret == 'string') return this.output(ret, true); 68 | if(Array.isArray(ret)) return this.output(...ret); 69 | const { message, isRepl } = ret; 70 | return this.output(message, isRepl); 71 | }); 72 | } 73 | 74 | output(data, isRepl) { 75 | if(!this.#output) return; 76 | this.#output(data, isRepl); 77 | } 78 | 79 | exit(code) { 80 | if(this.#exit) this.#exit(code); 81 | process.exit(code); 82 | } 83 | 84 | repl(command) { 85 | command = command.split(/\s+/); 86 | switch(command.shift()) { 87 | 88 | case 'r': 89 | case 'remake': 90 | case '/remake':return this.remake(); 91 | 92 | case 's': 93 | case 'select': 94 | case '/select': return this.select(...command); 95 | 96 | case 'u': 97 | case 'unselect': 98 | case '/unselect': return this.unselect(...command); 99 | 100 | case 'n': 101 | case 'next': 102 | case '/next': return this.next(true); 103 | 104 | case 'a': 105 | case 'alloc': 106 | case 'allocation': 107 | case '/alloc': 108 | case '/allocation': return this.alloc(...command); 109 | 110 | case 'rd': 111 | case 'random': 112 | case '/random': return this.random(); 113 | 114 | case 'at': 115 | case 'auto': 116 | case '/auto': return this.auto(...command); 117 | 118 | case 'x': 119 | case 'exit': 120 | case '/exit': return this.exit(0); 121 | 122 | case '?': 123 | case 'h': 124 | case 'help': 125 | case '/?': 126 | case '/h': 127 | case '/help': 128 | default: return this.help(...command); 129 | } 130 | } 131 | 132 | help(key) { 133 | 134 | switch(key) { 135 | case 'x': 136 | case 'exit': 137 | case '/exit': return `退出 138 | x, exit, /exit 命令同等效果`; 139 | 140 | case 'r': 141 | case 'remake': 142 | case '/remake': return `重开 143 | r, remake, /remake 命令同等效果`; 144 | 145 | case 's': 146 | case 'select': 147 | case '/select': return `选择 148 | s, select, /select 命令同等效果 149 | 150 | Example: /select 1 2 3 意味着选择 1 2 3 三个天赋 151 | 152 | /select [id2] [id3] 153 | 154 | 参数解释 通常来说要指定至少一个id 155 | 虽然不指定也可以 156 | [id2] 157 | [id3] 可以不指定`; 158 | 159 | case 'u': 160 | case 'unselect': 161 | case '/unselect': return `取消选择 162 | u, unselect, 163 | /unselect 命令同等效果 164 | 165 | Example: /unselect 1 2 3 166 | 意味着取消选择 1 2 3 三个天赋 167 | 168 | 参数解释 /unselect [id2] [id3] 169 | 170 | 通常来说要指定至少一个id 171 | 虽然不指定也可以 172 | [id2] 173 | [id3] 可以不指定`; 174 | 175 | 176 | case 'a': 177 | case 'alloc': 178 | case 'allocation': 179 | case '/alloc': 180 | case '/allocation': return `分配属性点 181 | a, alloc, allocation 182 | /alloc, /allocation 命令同等效果 183 | 184 | Example: /allocation STR 1 185 | /allocation INT -3 186 | /allocation CHR +5 187 | 188 | 参数解释 /allocation <[+/-]value> 189 | 190 | 表示要分配的属性标签 191 | 可选有 192 | CHR, chr, c, C 表示颜值 193 | INT, int, i, I 表示智力 194 | STR, str, s, S 表示体质 195 | MNY, mny, m, M 表示家境 196 | 必填 197 | 198 | <[+/-]value> 199 | 表示属性的调整 200 | 其中 201 | + 表示在当前基础上增加 202 | - 表示在当前基础上减少 203 | 无符号表示直接设置为此值 204 | 必填`; 205 | 206 | case 'n': 207 | case 'next': 208 | case '/next': return `继续 209 | n, next, /next 命令同等效果 210 | 211 | 效果 通常用于各步骤结束后 212 | 例如: 选择天赋后 213 | 分配属性后 214 | 每个年龄事件后 215 | 总评后 216 | 继承天赋后`; 217 | 218 | case 'at': 219 | case 'auto': 220 | case '/auto': return `自动播放 221 | at, auto, /auto 命令同等效果 222 | 223 | 效果 用于人生的过程中 224 | 每个年龄会自动下一年 225 | 播放速度 1 秒 1 年`; 226 | 227 | case '?': 228 | case 'h': 229 | case 'help': 230 | case '/?': 231 | case '/h': 232 | case '/help': return `显示帮助 233 | ?, h, help 234 | /?, /h, /help 命令同等效果 235 | 236 | Example: /help 237 | /help /select 238 | 239 | 参数解释 /help [command] 240 | 241 | [command] 要详细显示帮助的命令 242 | 可以不填`; 243 | } 244 | return `Help --- 245 | 命令 说明 示例 246 | x 247 | exit 248 | /exit 退出 /exit 249 | 250 | r 251 | remake 252 | /remake 重开 /remake 253 | 254 | s 255 | select 256 | /select 选择天赋 /select [id2] [id3] 257 | 258 | u 259 | unselect 260 | /unselect 取消选择 /unselect [id2] [id3] 261 | 262 | a 263 | alloc 264 | allocation 265 | /alloc 266 | /allocation 分配属性点 /allocation <[+/-]value> 267 | 268 | n 269 | next 270 | /next 继续 /next 271 | 272 | at 273 | auto 274 | /auto 自动播放 /auto 275 | 276 | ? 277 | h 278 | help 279 | /? 280 | /h 281 | /help 显示帮助 /help [command]`; 282 | } 283 | 284 | auto(arg) { 285 | this.#auto = arg != 'off'; 286 | return this.next(true); 287 | } 288 | 289 | remake() { 290 | if(this.#talentExtend) { 291 | this.#life.talentExtend(this.#talentExtend) 292 | global.dumpLocalStorage(); 293 | this.#talentExtend = null; 294 | } 295 | 296 | this.#isEnd = false; 297 | this.#talentSelected.clear(); 298 | this.#propertyAllocation = {CHR:0,INT:0,STR:0,MNY:0,SPR:5}; 299 | this.#step = this.Steps.TALENT; 300 | this.#randomTalents = this.#life.talentRandom(); 301 | return this.list(); 302 | } 303 | 304 | select(...select) { 305 | switch(this.#step) { 306 | case this.Steps.TALENT: return this.talentSelect(...select); 307 | case this.Steps.SUMMARY: return this.talentExtend(...select); 308 | } 309 | } 310 | 311 | unselect(...select) { 312 | switch(this.#step) { 313 | case this.Steps.TALENT: return this.talentUnSelect(...select); 314 | case this.Steps.SUMMARY: return this.talentExtendCancle(...select); 315 | } 316 | } 317 | 318 | talentSelect(...select) { 319 | const warn = str => `${this.list()}\n${this.style('warn', str)}`; 320 | for(const number of select) { 321 | const s = this.#randomTalents[number]; 322 | if(!s) return warn(`${number} 为未知天赋`); 323 | if(this.#talentSelected.has(s)) continue; 324 | if(this.#talentSelected.size == 3) 325 | return warn('⚠只能选3个天赋'); 326 | 327 | const exclusive = this.#life.exclusive( 328 | Array.from(this.#talentSelected).map(({id})=>id), 329 | s.id 330 | ); 331 | 332 | if(exclusive != null) 333 | for(const { name, id } of this.#talentSelected) 334 | if(id == exclusive) 335 | return warn(`天赋【${s.name}】与已选择的天赋【${name}】冲突`); 336 | 337 | this.#talentSelected.add(s); 338 | } 339 | 340 | return this.list(); 341 | } 342 | 343 | talentUnSelect(...select) { 344 | for(const number of select) { 345 | const s = this.#randomTalents[number]; 346 | if(this.#talentSelected.has(s)) 347 | this.#talentSelected.delete(s); 348 | } 349 | 350 | return this.list(); 351 | } 352 | 353 | talentExtend(select) { 354 | const warn = str => `${this.list()}\n${this.style('warn', str)}`; 355 | const list = Array.from(this.#talentSelected); 356 | const s = list[select]; 357 | if(!s) return warn(`${select} 为未知天赋`); 358 | this.#talentExtend = s.id; 359 | return this.list(); 360 | } 361 | 362 | talentExtendCancle() { 363 | this.#talentExtend = null; 364 | } 365 | 366 | list() { 367 | let description, list, check; 368 | switch(this.#step) { 369 | case this.Steps.TALENT: 370 | description = '🎉 请选择3个天赋'; 371 | list = this.#randomTalents; 372 | check = talent=>this.#talentSelected.has(talent); 373 | break; 374 | case this.Steps.SUMMARY: 375 | description = '🎉 你可以选一个天赋继承'; 376 | list = Array.from(this.#talentSelected); 377 | check = ({id})=>this.#talentExtend == id; 378 | break; 379 | } 380 | if(!list) return ''; 381 | 382 | return [description, list.map( 383 | (talent, i) => 384 | this.style( 385 | `grade${talent.grade}b`, 386 | `${check(talent)?'√':' '} ${i} ${talent.name}(${talent.description})` 387 | ) 388 | )] 389 | .flat() 390 | .join('\n'); 391 | } 392 | 393 | next(enter) { 394 | const warn = (a, b) => `${a}\n${this.style('warn', this.style('warn', b))}`; 395 | switch(this.#step) { 396 | case this.Steps.TALENT: 397 | if(this.#talentSelected.size != 3) return warn(this.list(), `⚠请选择3个天赋`); 398 | this.#step = this.Steps.PROPERTY; 399 | this.#propertyAllocation.total = 20 + this.#life.getTalentAllocationAddition( 400 | Array.from(this.#talentSelected).map(({id})=>id) 401 | ); 402 | this.#propertyAllocation.TLT = Array.from(this.#talentSelected).map(({id})=>id); 403 | return this.prop(); 404 | case this.Steps.PROPERTY: 405 | const less = this.less(); 406 | if(less > 0) return warn(this.prop(), `你还有${less}属性点没有分配完`); 407 | this.#step = this.Steps.TRAJECTORY; 408 | delete this.#propertyAllocation.total; 409 | this.#life.restart(this.#propertyAllocation); 410 | return this.trajectory(enter); 411 | case this.Steps.TRAJECTORY: 412 | if(!this.#isEnd) return this.trajectory(enter); 413 | this.#step = this.Steps.SUMMARY; 414 | return `${ 415 | this.summary() 416 | }\n\n${ 417 | this.list() 418 | }`; 419 | case this.Steps.SUMMARY: 420 | return this.remake(); 421 | } 422 | } 423 | 424 | trajectory(enter) { 425 | if(enter) { 426 | if(this.#interval) { 427 | clearInterval(this.#interval); 428 | this.#interval = null; 429 | this.#auto = false; 430 | } else if(this.#auto) { 431 | this.#interval = setInterval( 432 | ()=>{ 433 | const trajectory = this.next(); 434 | if(this.#isEnd && this.#interval) { 435 | clearInterval(this.#interval); 436 | this.#interval = null; 437 | } 438 | if(!this.#isEnd) return this.output(`${trajectory}\n`); 439 | return this.output(trajectory, true); 440 | } 441 | , 1000); 442 | return; 443 | } 444 | } 445 | 446 | const trajectory = this.#life.next(); 447 | const { age, content, isEnd } = trajectory; 448 | if(isEnd) this.#isEnd = true; 449 | return `${age}岁:\t${ 450 | content.map( 451 | ({type, description, grade, name, postEvent}) => { 452 | switch(type) { 453 | case 'TLT': 454 | return `天赋【${name}】发动:${description}`; 455 | case 'EVT': 456 | return description + (postEvent?`\n\t${postEvent}`:''); 457 | } 458 | } 459 | ).join('\n\t') 460 | }`; 461 | } 462 | 463 | prop() { 464 | const { CHR, INT, STR, MNY } = this.#propertyAllocation; 465 | return `🎉属性分配 466 | 剩余点数 ${this.less()} 467 | 468 | 属性(TAG) 当前值 469 | 颜值(CHR) ${CHR} 470 | 智力(INT) ${INT} 471 | 体质(STR) ${STR} 472 | 家境(MNY) ${MNY} 473 | ` 474 | } 475 | 476 | less() { 477 | const { total, CHR, INT, STR, MNY } = this.#propertyAllocation; 478 | return total - CHR - INT - STR - MNY; 479 | } 480 | 481 | alloc(tag, value) { 482 | const warn = str => `${this.prop()}\n${this.style('warn', str)}` 483 | if(!value) return warn('⚠ 分配的数值没有给定'); 484 | const isSet = !(value[0] == '-'|| value[0] == '+'); 485 | 486 | value = Number(value); 487 | if(isNaN(value)) return warn('⚠ 分配的数值不正确'); 488 | 489 | switch(tag) { 490 | case 'c': 491 | case 'chr': 492 | case 'C': tag = 'CHR'; break; 493 | case 'i': 494 | case 'int': 495 | case 'I': tag = 'INT'; break; 496 | case 's': 497 | case 'S': 498 | case 'str': tag = 'STR'; break; 499 | case 'm': 500 | case 'M': 501 | case 'mny': tag = 'MNY'; break; 502 | } 503 | 504 | 505 | switch(tag) { 506 | case 'CHR': 507 | case 'INT': 508 | case 'STR': 509 | case 'MNY': 510 | if(isSet) value = value - this.#propertyAllocation[tag]; 511 | 512 | const tempLess = this.less() - value; 513 | const tempSet = this.#propertyAllocation[tag] + value; 514 | 515 | if(tempLess<0) return warn('⚠ 你没有更多的点数可以分配了'); 516 | if( 517 | tempLess>this.#propertyAllocation.total 518 | || tempSet < 0 519 | ) return warn('⚠ 不能分配负数属性'); 520 | if(tempSet>10) return warn('⚠ 单项属性最高分配10点'); 521 | 522 | this.#propertyAllocation[tag] += value; 523 | 524 | return this.prop(); 525 | 526 | default: 527 | return warn('⚠ 未知的tag'); 528 | } 529 | } 530 | 531 | random() { 532 | let t = this.#propertyAllocation.total; 533 | const arr = [10, 10, 10, 10]; 534 | while(t>0) { 535 | const sub = Math.round(Math.random() * (Math.min(t, 10) - 1)) + 1; 536 | while(true) { 537 | const select = Math.floor(Math.random() * 4) % 4; 538 | if(arr[select] - sub <0) continue; 539 | arr[select] -= sub; 540 | t -= sub; 541 | break; 542 | } 543 | } 544 | this.#propertyAllocation.CHR = 10 - arr[0]; 545 | this.#propertyAllocation.INT = 10 - arr[1]; 546 | this.#propertyAllocation.STR = 10 - arr[2]; 547 | this.#propertyAllocation.MNY = 10 - arr[3]; 548 | return this.prop(); 549 | } 550 | 551 | summary() { 552 | 553 | const records = this.#life.getRecord(); 554 | const s = (type, func)=>{ 555 | const value = func(records.map(({[type]:v})=>v)); 556 | const { judge, grade } = summary(type, value); 557 | return { judge, grade, value }; 558 | }; 559 | 560 | const style = (name, grade, judge, value) => this.style(`grade${grade}b`, `${name}:${value} ${judge}`); 561 | const judge = (name, type, func) => { 562 | const { judge, grade, value } = s(type, func); 563 | return style(name, grade, judge, value ); 564 | } 565 | 566 | return [ 567 | '🎉 总评', 568 | judge('颜值', 'CHR', max), 569 | judge('智力', 'INT', max), 570 | judge('体质', 'STR', max), 571 | judge('家境', 'MNY', max), 572 | judge('快乐', 'SPR', max), 573 | judge('享年', 'AGE', max), 574 | (()=>{ 575 | const m = type=>max(records.map(({[type]: value})=>value)); 576 | const value = Math.floor(sum(m('CHR'), m('INT'), m('STR'), m('MNY'), m('SPR'))*2 + m('AGE')/2); 577 | const { judge, grade } = summary('SUM', value); 578 | return style('总评', grade, judge, value ); 579 | })(), 580 | ].join('\n'); 581 | } 582 | } 583 | 584 | export default App; -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import { max, sum } from './functions/util.js'; 2 | import { summary } from './functions/summary.js' 3 | import Life from './life.js' 4 | 5 | class App{ 6 | constructor(){ 7 | this.#life = new Life(); 8 | } 9 | 10 | #life; 11 | #pages; 12 | #talentSelected = new Set(); 13 | #totalMax=20; 14 | #isEnd = false; 15 | #selectedExtendTalent = null; 16 | #hintTimeout; 17 | 18 | async initial() { 19 | this.initPages(); 20 | this.switch('loading'); 21 | await this.#life.initial(); 22 | this.switch('index'); 23 | window.onerror = (event, source, lineno, colno, error) => { 24 | this.hint(`[ERROR] at (${source}:${lineno}:${colno})\n\n${error?.stack||error||'unknow Error'}`, 'error'); 25 | } 26 | } 27 | 28 | initPages() { 29 | 30 | // Loading 31 | const loadingPage = $(` 32 |
33 |
34 | 人生重开模拟器
35 |
加载中...
36 |
37 |
38 | `); 39 | 40 | // Index 41 | const indexPage = $(` 42 |
43 |
已重开1次
44 | 45 | 46 |
47 | 人生重开模拟器
48 |
这垃圾人生一秒也不想呆了
49 |
50 | 51 |
52 | `); 53 | 54 | // Init theme 55 | this.setTheme(localStorage.getItem('theme')) 56 | 57 | indexPage 58 | .find('#restart') 59 | .click(()=>this.switch('talent')); 60 | 61 | indexPage 62 | .find('#rank') 63 | .click(()=>this.hint('别卷了!没有排行榜')); 64 | 65 | indexPage 66 | .find("#themeToggleBtn") 67 | .click(() => { 68 | if(localStorage.getItem('theme') == 'light') { 69 | localStorage.setItem('theme', 'dark'); 70 | } else { 71 | localStorage.setItem('theme', 'light'); 72 | } 73 | 74 | this.setTheme(localStorage.getItem('theme')) 75 | }); 76 | 77 | // Talent 78 | const talentPage = $(` 79 |
80 |
天赋抽卡
81 | 82 |
    83 | 84 |
    85 | `); 86 | 87 | const createTalent = ({ grade, name, description }) => { 88 | return $(`
  • ${name}(${description})
  • `) 89 | }; 90 | 91 | talentPage 92 | .find('#random') 93 | .click(()=>{ 94 | talentPage.find('#random').hide(); 95 | const ul = talentPage.find('#talents'); 96 | this.#life.talentRandom() 97 | .forEach(talent=>{ 98 | const li = createTalent(talent); 99 | ul.append(li); 100 | li.click(()=>{ 101 | if(li.hasClass('selected')) { 102 | li.removeClass('selected') 103 | this.#talentSelected.delete(talent); 104 | if(this.#talentSelected.size<3) { 105 | talentPage.find('#next').text('请选择3个') 106 | } 107 | } else { 108 | if(this.#talentSelected.size==3) { 109 | this.hint('只能选3个天赋'); 110 | return; 111 | } 112 | 113 | const exclusive = this.#life.exclusive( 114 | Array.from(this.#talentSelected).map(({id})=>id), 115 | talent.id 116 | ); 117 | if(exclusive != null) { 118 | for(const { name, id } of this.#talentSelected) { 119 | if(id == exclusive) { 120 | this.hint(`与已选择的天赋【${name}】冲突`); 121 | return; 122 | } 123 | } 124 | return; 125 | } 126 | li.addClass('selected'); 127 | this.#talentSelected.add(talent); 128 | if(this.#talentSelected.size==3) { 129 | talentPage.find('#next').text('开始新人生') 130 | } 131 | } 132 | }); 133 | }); 134 | }); 135 | 136 | talentPage 137 | .find('#next') 138 | .click(()=>{ 139 | if(this.#talentSelected.size!=3) { 140 | this.hint('请选择3个天赋'); 141 | return; 142 | } 143 | this.#totalMax = 20 + this.#life.getTalentAllocationAddition(Array.from(this.#talentSelected).map(({id})=>id)); 144 | this.switch('property'); 145 | }) 146 | 147 | // Property 148 | // hint of extension tobermory.es6-string-html 149 | const propertyPage = $(/*html*/` 150 |
    151 |
    152 | 调整初始属性
    153 |
    可用属性点:0
    154 |
    155 |
      156 |
        157 | 158 | 159 |
        160 | `); 161 | propertyPage.mounted = ()=>{ 162 | propertyPage 163 | .find('#talentSelectedView').append( 164 | `
      • 已选天赋
      • ` + 165 | Array.from(this.#talentSelected) 166 | .map(({name,description})=>`
      • ${name}(${description})
      • `) 167 | .join('') 168 | ) 169 | } 170 | const groups = {}; 171 | const total = ()=>{ 172 | let t = 0; 173 | for(const type in groups) 174 | t += groups[type].get(); 175 | return t; 176 | } 177 | const freshTotal = ()=>{ 178 | propertyPage.find('#total').text(`可用属性点:${this.#totalMax - total()}`); 179 | } 180 | const getBtnGroups = (name, min, max)=>{ 181 | const group = $(`
      • ${name}      
      • `); 182 | const btnSub = $(``); 183 | const inputBox = $(``); 184 | const btnAdd = $(``); 185 | group.append(btnSub); 186 | group.append(inputBox); 187 | group.append(btnAdd); 188 | 189 | const limit = v=>{ 190 | v = Number(v)||0; 191 | v = Math.round(v); 192 | return v < min ? min : ( 193 | v > max ? max : v 194 | ) 195 | } 196 | const get = ()=>Number(inputBox.val()); 197 | const set = v=>{ 198 | inputBox.val(limit(v)); 199 | freshTotal(); 200 | } 201 | btnAdd.click(()=>{ 202 | if(total() >= this.#totalMax) { 203 | this.hint('没有可分配的点数了'); 204 | return; 205 | } 206 | set(get()+1); 207 | }); 208 | btnSub.click(()=>set(get()-1)); 209 | inputBox.on('input', ()=>{ 210 | const t = total(); 211 | let val = get(); 212 | if(t > this.#totalMax) { 213 | val -= t - this.#totalMax; 214 | } 215 | val = limit(val); 216 | if(val != inputBox.val()) { 217 | set(val); 218 | } 219 | freshTotal(); 220 | }); 221 | return {group, get, set}; 222 | } 223 | 224 | groups.CHR = getBtnGroups("颜值", 0, 10); // 颜值 charm CHR 225 | groups.INT = getBtnGroups("智力", 0, 10); // 智力 intelligence INT 226 | groups.STR = getBtnGroups("体质", 0, 10); // 体质 strength STR 227 | groups.MNY = getBtnGroups("家境", 0, 10); // 家境 money MNY 228 | 229 | const ul = propertyPage.find('#propertyAllocation'); 230 | 231 | for(const type in groups) { 232 | ul.append(groups[type].group); 233 | } 234 | 235 | propertyPage 236 | .find('#random') 237 | .click(()=>{ 238 | let t = this.#totalMax; 239 | const arr = [10, 10, 10, 10]; 240 | while(t>0) { 241 | const sub = Math.round(Math.random() * (Math.min(t, 10) - 1)) + 1; 242 | while(true) { 243 | const select = Math.floor(Math.random() * 4) % 4; 244 | if(arr[select] - sub <0) continue; 245 | arr[select] -= sub; 246 | t -= sub; 247 | break; 248 | } 249 | } 250 | groups.CHR.set(10 - arr[0]); 251 | groups.INT.set(10 - arr[1]); 252 | groups.STR.set(10 - arr[2]); 253 | groups.MNY.set(10 - arr[3]); 254 | }); 255 | 256 | propertyPage 257 | .find('#start') 258 | .click(()=>{ 259 | if(total() < this.#totalMax) { 260 | this.hint(`你还有${this.#totalMax-total()}属性点没有分配完`); 261 | return; 262 | } else if (total() > this.#totalMax) { 263 | this.hint(`你多使用了${total() - this.#totalMax}属性点`); 264 | return; 265 | } 266 | this.#life.restart({ 267 | CHR: groups.CHR.get(), 268 | INT: groups.INT.get(), 269 | STR: groups.STR.get(), 270 | MNY: groups.MNY.get(), 271 | SPR: 5, 272 | TLT: Array.from(this.#talentSelected).map(({id})=>id), 273 | }); 274 | this.switch('trajectory'); 275 | this.#pages.trajectory.born(); 276 | $(document).keydown(function(event){ 277 | if(event.which == 32 || event.which == 13){ 278 | $('#lifeTrajectory').click(); 279 | } 280 | }) 281 | }); 282 | 283 | // Trajectory 284 | const trajectoryPage = $(` 285 |
        286 |
          287 |
            288 | 289 |
            290 | `); 291 | 292 | trajectoryPage 293 | .find('#lifeTrajectory') 294 | .click(()=>{ 295 | if(this.#isEnd) return; 296 | const trajectory = this.#life.next(); 297 | const { age, content, isEnd } = trajectory; 298 | 299 | const li = $(`
          • ${age}岁:${ 300 | content.map( 301 | ({type, description, grade, name, postEvent}) => { 302 | switch(type) { 303 | case 'TLT': 304 | return `天赋【${name}】发动:${description}`; 305 | case 'EVT': 306 | return description + (postEvent?`
            ${postEvent}`:''); 307 | } 308 | } 309 | ).join('
            ') 310 | }
          • `); 311 | li.appendTo('#lifeTrajectory'); 312 | $("#lifeTrajectory").scrollTop($("#lifeTrajectory")[0].scrollHeight); 313 | if(isEnd) { 314 | $(document).unbind("keydown"); 315 | this.#isEnd = true; 316 | trajectoryPage.find('#summary').show(); 317 | } else { 318 | // 如未死亡,更新数值 319 | // Update properties if not die yet 320 | const property = this.#life.getLastRecord(); 321 | $("#lifeProperty").html(` 322 |
          • 颜值:${property.CHR}
          • 323 |
          • 智力:${property.INT}
          • 324 |
          • 体质:${property.STR}
          • 325 |
          • 家境:${property.MNY}
          • 326 |
          • 快乐:${property.SPR}
          • `); 327 | } 328 | }); 329 | 330 | trajectoryPage 331 | .find('#summary') 332 | .click(()=>{ 333 | this.switch('summary'); 334 | }) 335 | 336 | // Summary 337 | const summaryPage = $(` 338 |
            339 |
            人生总结
            340 |
              341 |
            • 颜值:9级 美若天仙
            • 342 |
            • 智力:4级 智力一般
            • 343 |
            • 体质:1级 极度虚弱
            • 344 |
            • 家境:6级 小康之家
            • 345 |
            • 享年:3岁 早夭
            • 346 |
            • 快乐:3级 不太幸福的人生
            • 347 |
            348 |
            天赋,你可以选一个,下辈子还能抽到
            349 |
              350 |
            • 黑幕(面试一定成功)
            • 351 |
            352 | 353 |
            354 | `); 355 | 356 | summaryPage 357 | .find('#again') 358 | .click(()=>{ 359 | this.times ++; 360 | this.#life.talentExtend(this.#selectedExtendTalent); 361 | this.#selectedExtendTalent = null; 362 | this.#talentSelected.clear(); 363 | this.#totalMax = 20; 364 | this.#isEnd = false; 365 | this.switch('index'); 366 | }); 367 | 368 | this.#pages = { 369 | loading: { 370 | page: loadingPage, 371 | clear: ()=>{}, 372 | }, 373 | index: { 374 | page: indexPage, 375 | btnRank: indexPage.find('#rank'), 376 | btnRestart: indexPage.find('#restart'), 377 | hint: indexPage.find('.hint'), 378 | cnt: indexPage.find('#cnt'), 379 | clear: ()=>{ 380 | indexPage.find('.hint').hide(); 381 | 382 | const times = this.times; 383 | const btnRank = indexPage.find('#rank'); 384 | const cnt = indexPage.find('#cnt'); 385 | if(times > 0) { 386 | btnRank.show(); 387 | cnt.show(); 388 | cnt.text(`已重开${times}次`); 389 | return; 390 | } 391 | 392 | btnRank.hide(); 393 | cnt.hide(); 394 | }, 395 | }, 396 | talent: { 397 | page: talentPage, 398 | clear: ()=>{ 399 | talentPage.find('ul.selectlist').empty(); 400 | talentPage.find('#random').show(); 401 | this.#totalMax = 20; 402 | }, 403 | }, 404 | property: { 405 | page: propertyPage, 406 | clear: ()=>{ 407 | freshTotal(); 408 | propertyPage 409 | .find('#talentSelectedView') 410 | .empty(); 411 | }, 412 | }, 413 | trajectory: { 414 | page: trajectoryPage, 415 | clear: ()=>{ 416 | trajectoryPage.find('#lifeTrajectory').empty(); 417 | trajectoryPage.find('#summary').hide(); 418 | this.#isEnd = false; 419 | }, 420 | born: ()=>{ 421 | trajectoryPage.find('#lifeTrajectory').trigger("click"); 422 | } 423 | }, 424 | summary: { 425 | page: summaryPage, 426 | clear: ()=>{ 427 | const judge = summaryPage.find('#judge'); 428 | const talents = summaryPage.find('#talents'); 429 | judge.empty(); 430 | talents.empty(); 431 | this.#talentSelected.forEach(talent=>{ 432 | const li = createTalent(talent); 433 | talents.append(li); 434 | li.click(()=>{ 435 | if(li.hasClass('selected')) { 436 | this.#selectedExtendTalent = null; 437 | li.removeClass('selected'); 438 | } else if(this.#selectedExtendTalent != null) { 439 | this.hint('只能继承一个天赋'); 440 | return; 441 | } else { 442 | this.#selectedExtendTalent = talent.id; 443 | li.addClass('selected'); 444 | } 445 | }); 446 | }); 447 | 448 | const records = this.#life.getRecord(); 449 | const s = (type, func)=>{ 450 | const value = func(records.map(({[type]:v})=>v)); 451 | const { judge, grade } = summary(type, value); 452 | return { judge, grade, value }; 453 | }; 454 | console.table(records); 455 | console.debug(records); 456 | 457 | judge.append([ 458 | (()=>{ 459 | const { judge, grade, value } = s('CHR', max); 460 | return `
          • 颜值:${value} ${judge}
          • ` 461 | })(), 462 | (()=>{ 463 | const { judge, grade, value } = s('INT', max); 464 | return `
          • 智力:${value} ${judge}
          • ` 465 | })(), 466 | (()=>{ 467 | const { judge, grade, value } = s('STR', max); 468 | return `
          • 体质:${value} ${judge}
          • ` 469 | })(), 470 | (()=>{ 471 | const { judge, grade, value } = s('MNY', max); 472 | return `
          • 家境:${value} ${judge}
          • ` 473 | })(), 474 | (()=>{ 475 | const { judge, grade, value } = s('SPR', max); 476 | return `
          • 快乐:${value} ${judge}
          • ` 477 | })(), 478 | (()=>{ 479 | const { judge, grade, value } = s('AGE', max); 480 | return `
          • 享年:${value} ${judge}
          • ` 481 | })(), 482 | (()=>{ 483 | const m = type=>max(records.map(({[type]: value})=>value)); 484 | const value = Math.floor(sum(m('CHR'), m('INT'), m('STR'), m('MNY'), m('SPR'))*2 + m('AGE')/2); 485 | const { judge, grade } = summary('SUM', value); 486 | return `
          • 总评:${value} ${judge}
          • ` 487 | })(), 488 | ].join('')); 489 | } 490 | } 491 | } 492 | } 493 | 494 | switch(page) { 495 | const p = this.#pages[page]; 496 | if(!p) return; 497 | $('#main').detach(); 498 | p.clear(); 499 | p.page.appendTo('body'); 500 | if(typeof p.page.mounted === 'function'){ 501 | p.page.mounted() 502 | } 503 | } 504 | 505 | hint(message, type='info') { 506 | if(this.#hintTimeout) { 507 | clearTimeout(this.#hintTimeout); 508 | this.#hintTimeout = null; 509 | } 510 | hideBanners(); 511 | requestAnimationFrame(() => { 512 | const banner = $(`.banner.${type}`); 513 | banner.addClass('visible'); 514 | banner.find('.banner-message').text(message); 515 | if(type != 'error') { 516 | this.#hintTimeout = setTimeout(hideBanners, 3000); 517 | } 518 | }); 519 | } 520 | 521 | setTheme(theme) { 522 | const themeLink = $(document).find('#themeLink'); 523 | 524 | if(theme == 'light') { 525 | themeLink.attr('href', 'light.css'); 526 | } else { 527 | themeLink.attr('href', 'dark.css'); 528 | } 529 | } 530 | 531 | get times() {return JSON.parse(localStorage.times||'0') || 0;} 532 | set times(v) {localStorage.times = JSON.stringify(parseInt(v) || 0)}; 533 | 534 | } 535 | 536 | export default App; 537 | -------------------------------------------------------------------------------- /data/talents.json: -------------------------------------------------------------------------------- 1 | { 2 | "1001": { 3 | "id": "1001", 4 | "name": "随身玉佩", 5 | "description": "或许有护佑作用", 6 | "grade": 0 7 | }, 8 | "1002": { 9 | "id": "1002", 10 | "name": "红肚兜", 11 | "description": "小时候死亡率降低", 12 | "grade": 0 13 | }, 14 | "1003": { 15 | "id": "1003", 16 | "name": "生而为男", 17 | "description": "性别一定为男", 18 | "grade": 1, 19 | "exclusive": [ 20 | "1004", 21 | "1025", 22 | "1024", 23 | 1113 24 | ] 25 | }, 26 | "1004": { 27 | "id": "1004", 28 | "name": "生而为女", 29 | "description": "性别一定为女", 30 | "grade": 1, 31 | "exclusive": [ 32 | "1003", 33 | "1024", 34 | "1025" 35 | ] 36 | }, 37 | "1005": { 38 | "id": "1005", 39 | "name": "动漫高手", 40 | "description": "入宅的可能性翻6倍", 41 | "grade": 2 42 | }, 43 | "1006": { 44 | "id": "1006", 45 | "name": "乐天派", 46 | "description": "快乐+1", 47 | "grade": 0, 48 | "effect": { 49 | "SPR": 1 50 | } 51 | }, 52 | "1007": { 53 | "id": "1007", 54 | "name": "天赋异禀", 55 | "description": "初始可用属性点+2", 56 | "grade": 1, 57 | "status": 2 58 | }, 59 | "1008": { 60 | "id": "1008", 61 | "name": "天生抑郁", 62 | "description": "快乐-3", 63 | "grade": 0, 64 | "effect": { 65 | "SPR": -3 66 | } 67 | }, 68 | "1009": { 69 | "id": "1009", 70 | "name": "网络巨魔", 71 | "description": "快乐+2", 72 | "grade": 1, 73 | "effect": { 74 | "SPR": 2 75 | } 76 | }, 77 | "1010": { 78 | "id": "1010", 79 | "name": "天龙人", 80 | "description": "你拥有北京户口", 81 | "grade": 2, 82 | "exclusive": [ 83 | "1012", 84 | "1013", 85 | "1014" 86 | ] 87 | }, 88 | "1011": { 89 | "id": "1011", 90 | "name": "独生子女", 91 | "description": "你没有兄弟姐妹", 92 | "grade": 0 93 | }, 94 | "1012": { 95 | "id": "1012", 96 | "name": "乡间微风", 97 | "description": "你出生在农村", 98 | "grade": 0, 99 | "exclusive": [ 100 | "1010", 101 | "1013", 102 | "1014" 103 | ] 104 | }, 105 | "1013": { 106 | "id": "1013", 107 | "name": "城中高楼", 108 | "description": "你出生在城市", 109 | "grade": 0, 110 | "exclusive": [ 111 | "1010", 112 | "1012", 113 | "1014" 114 | ] 115 | }, 116 | "1014": { 117 | "id": "1014", 118 | "name": "美籍华人", 119 | "description": "你有美国国籍", 120 | "grade": 2, 121 | "effect": { 122 | "MNY": 3 123 | }, 124 | "exclusive": [ 125 | "1010", 126 | "1012", 127 | "1013" 128 | ] 129 | }, 130 | "1015": { 131 | "id": "1015", 132 | "name": "家中老大", 133 | "description": "你最受父母宠爱", 134 | "grade": 1 135 | }, 136 | "1016": { 137 | "id": "1016", 138 | "name": "水性良好", 139 | "description": "不会被淹死", 140 | "grade": 0 141 | }, 142 | "1017": { 143 | "id": "1017", 144 | "name": "先天免疫", 145 | "description": "你不会得艾滋病", 146 | "grade": 0 147 | }, 148 | "1018": { 149 | "id": "1018", 150 | "name": "人类进化", 151 | "description": "所有属性+1", 152 | "grade": 2, 153 | "effect": { 154 | "SPR": 1, 155 | "MNY": 1, 156 | "CHR": 1, 157 | "STR": 1, 158 | "INT": 1 159 | } 160 | }, 161 | "1019": { 162 | "id": "1019", 163 | "name": "超凡", 164 | "description": "初始可用属性点+4", 165 | "grade": 2, 166 | "status": 4 167 | }, 168 | "1020": { 169 | "id": "1020", 170 | "name": "父母美貌", 171 | "description": "颜值+2", 172 | "grade": 1, 173 | "effect": { 174 | "CHR": 2 175 | } 176 | }, 177 | "1021": { 178 | "id": "1021", 179 | "name": "红颜薄命", 180 | "description": "颜值+2,体质-2", 181 | "grade": 0, 182 | "effect": { 183 | "CHR": 2, 184 | "STR": -2 185 | } 186 | }, 187 | "1022": { 188 | "id": "1022", 189 | "name": "属蛇", 190 | "description": "不会被蛇咬死", 191 | "grade": 0 192 | }, 193 | "1023": { 194 | "id": "1023", 195 | "name": "半神", 196 | "description": "所有属性+2", 197 | "grade": 3, 198 | "effect": { 199 | "SPR": 2, 200 | "MNY": 2, 201 | "CHR": 2, 202 | "STR": 2, 203 | "INT": 2 204 | } 205 | }, 206 | "1024": { 207 | "id": "1024", 208 | "name": "人中龙凤", 209 | "description": "天生双重性别", 210 | "grade": 2, 211 | "exclusive": [ 212 | "1003", 213 | "1004", 214 | "1025" 215 | ] 216 | }, 217 | "1025": { 218 | "id": "1025", 219 | "name": "阴阳之外", 220 | "description": "天生无性别", 221 | "grade": 2, 222 | "exclusive": [ 223 | "1003", 224 | "1024", 225 | "1004", 226 | 1113 227 | ] 228 | }, 229 | "1026": { 230 | "id": "1026", 231 | "name": "彩虹之下", 232 | "description": "可能和同性交往", 233 | "grade": 0, 234 | "exclusive": [ 235 | "1113" 236 | ] 237 | }, 238 | "1027": { 239 | "id": "1027", 240 | "name": "斩情证道", 241 | "description": "终生不恋爱结婚", 242 | "grade": 1, 243 | "exclusive": [ 244 | "1113" 245 | ] 246 | }, 247 | "1028": { 248 | "id": "1028", 249 | "name": "桃花连连", 250 | "description": "恋爱机会提升", 251 | "grade": 0 252 | }, 253 | "1029": { 254 | "id": "1029", 255 | "name": "平安童年", 256 | "description": "12岁前父母都健在", 257 | "grade": 1 258 | }, 259 | "1030": { 260 | "id": "1030", 261 | "name": "宠物大师", 262 | "description": "宠物不会意外死亡", 263 | "grade": 0 264 | }, 265 | "1031": { 266 | "id": "1031", 267 | "name": "天生残疾", 268 | "description": "体质-2", 269 | "grade": 0, 270 | "effect": { 271 | "STR": -2 272 | } 273 | }, 274 | "1032": { 275 | "id": "1032", 276 | "name": "早产儿", 277 | "description": "所有属性-1", 278 | "grade": 0, 279 | "effect": { 280 | "SPR": -1, 281 | "MNY": -1, 282 | "CHR": -1, 283 | "STR": -1, 284 | "INT": -1 285 | } 286 | }, 287 | "1033": { 288 | "id": "1033", 289 | "name": "十死无生", 290 | "description": "体质-10", 291 | "grade": 0, 292 | "effect": { 293 | "STR": -10 294 | } 295 | }, 296 | "1034": { 297 | "id": "1034", 298 | "name": "家运不顺", 299 | "description": "家境-2", 300 | "grade": 0, 301 | "effect": { 302 | "MNY": -2 303 | } 304 | }, 305 | "1035": { 306 | "id": "1035", 307 | "name": "头着地", 308 | "description": "智力-2", 309 | "grade": 0, 310 | "effect": { 311 | "INT": -2 312 | } 313 | }, 314 | "1036": { 315 | "id": "1036", 316 | "name": "胎教", 317 | "description": "智力+1", 318 | "grade": 0, 319 | "effect": { 320 | "INT": 1 321 | } 322 | }, 323 | "1037": { 324 | "id": "1037", 325 | "name": "班中红人", 326 | "description": "和同学容易处好关系", 327 | "grade": 0 328 | }, 329 | "1038": { 330 | "id": "1038", 331 | "name": "骑士", 332 | "description": "能轻松学会骑车", 333 | "grade": 0 334 | }, 335 | "1039": { 336 | "id": "1039", 337 | "name": "永远的神", 338 | "description": "电竞天才", 339 | "grade": 1 340 | }, 341 | "1040": { 342 | "id": "1040", 343 | "name": "戒律", 344 | "description": "赌毒不沾", 345 | "grade": 0 346 | }, 347 | "1041": { 348 | "id": "1041", 349 | "name": "丁克", 350 | "description": "不生孩子", 351 | "grade": 1, 352 | "exclusive": [ 353 | "1113" 354 | ] 355 | }, 356 | "1042": { 357 | "id": "1042", 358 | "name": "少数民族", 359 | "description": "高考+5分", 360 | "grade": 0 361 | }, 362 | "1043": { 363 | "id": "1043", 364 | "name": "老司机", 365 | "description": "你和家人不会发生车祸", 366 | "grade": 0 367 | }, 368 | "1044": { 369 | "id": "1044", 370 | "name": "低压", 371 | "description": "你的家人不会心脏病", 372 | "grade": 0 373 | }, 374 | "1045": { 375 | "id": "1045", 376 | "name": "战功", 377 | "description": "你退伍后会当官", 378 | "grade": 0 379 | }, 380 | "1046": { 381 | "id": "1046", 382 | "name": "不孕不育", 383 | "description": "你生不出孩子", 384 | "grade": 1, 385 | "exclusive": [ 386 | "1113" 387 | ] 388 | }, 389 | "1047": { 390 | "id": "1047", 391 | "name": "白头偕老", 392 | "description": "爱人至少能活到70岁", 393 | "grade": 1 394 | }, 395 | "1048": { 396 | "id": "1048", 397 | "name": "神秘的小盒子", 398 | "description": "100岁时才能开启", 399 | "grade": 3 400 | }, 401 | "1049": { 402 | "id": "1049", 403 | "name": "三十而立", 404 | "description": "30岁时家境+2", 405 | "condition": "AGE?[30]", 406 | "grade": 0, 407 | "effect": { 408 | "MNY": 2 409 | } 410 | }, 411 | "1050": { 412 | "id": "1050", 413 | "name": "四十不惑", 414 | "description": "40岁时智力+2", 415 | "condition": "AGE?[40]", 416 | "grade": 0, 417 | "effect": { 418 | "INT": 2 419 | } 420 | }, 421 | "1051": { 422 | "id": "1051", 423 | "name": "知天命", 424 | "description": "50岁时智力、快乐+1", 425 | "condition": "AGE?[50]", 426 | "grade": 0, 427 | "effect": { 428 | "SPR": 1, 429 | "INT": 1 430 | } 431 | }, 432 | "1052": { 433 | "id": "1052", 434 | "name": "耳顺", 435 | "description": "60岁时快乐+2", 436 | "condition": "AGE?[60]", 437 | "grade": 0, 438 | "effect": { 439 | "SPR": 2 440 | } 441 | }, 442 | "1053": { 443 | "id": "1053", 444 | "name": "从心所欲", 445 | "description": "70岁时快乐+3", 446 | "condition": "AGE?[70]", 447 | "grade": 0, 448 | "effect": { 449 | "SPR": 3 450 | } 451 | }, 452 | "1054": { 453 | "id": "1054", 454 | "name": "老当益壮", 455 | "description": "60岁时体质+2", 456 | "condition": "AGE?[60]", 457 | "grade": 1, 458 | "effect": { 459 | "STR": 2 460 | } 461 | }, 462 | "1055": { 463 | "id": "1055", 464 | "name": "鹤发童颜", 465 | "description": "70岁时颜值+3", 466 | "condition": "AGE?[70]", 467 | "grade": 0, 468 | "effect": { 469 | "CHR": 3 470 | } 471 | }, 472 | "1056": { 473 | "id": "1056", 474 | "name": "学前启蒙", 475 | "description": "5岁时智力+2", 476 | "condition": "AGE?[5]", 477 | "grade": 1, 478 | "effect": { 479 | "INT": 2 480 | } 481 | }, 482 | "1057": { 483 | "id": "1057", 484 | "name": "十八变", 485 | "description": "18岁时颜值+2", 486 | "condition": "AGE?[18]", 487 | "grade": 1, 488 | "effect": { 489 | "CHR": 2 490 | } 491 | }, 492 | "1058": { 493 | "id": "1058", 494 | "name": "迟来之财", 495 | "description": "90岁时家境+4", 496 | "condition": "AGE?[90]", 497 | "grade": 0, 498 | "effect": { 499 | "MNY": 4 500 | } 501 | }, 502 | "1059": { 503 | "id": "1059", 504 | "name": "理财达人", 505 | "description": "30、40、50岁时家境+1", 506 | "condition": "AGE?[30,40,50]", 507 | "grade": 0, 508 | "effect": { 509 | "MNY": 1 510 | } 511 | }, 512 | "1060": { 513 | "id": "1060", 514 | "name": "成熟", 515 | "description": "12、18岁时智力+1", 516 | "condition": "AGE?[12,18]", 517 | "grade": 1, 518 | "effect": { 519 | "INT": 1 520 | } 521 | }, 522 | "1061": { 523 | "id": "1061", 524 | "name": "形象管理", 525 | "description": "16、24岁时颜值+1", 526 | "condition": "AGE?[16,24]", 527 | "grade": 1, 528 | "effect": { 529 | "CHR": 1 530 | } 531 | }, 532 | "1062": { 533 | "id": "1062", 534 | "name": "成年礼", 535 | "description": "18岁时快乐+1", 536 | "condition": "AGE?[18]", 537 | "grade": 0, 538 | "effect": { 539 | "SPR": 1 540 | } 541 | }, 542 | "1063": { 543 | "id": "1063", 544 | "name": "开光之胎", 545 | "description": "初始可用属性点+1", 546 | "grade": 0, 547 | "status": 1 548 | }, 549 | "1064": { 550 | "id": "1064", 551 | "name": "天命", 552 | "description": "初始可用属性点+8", 553 | "grade": 3, 554 | "status": 8 555 | }, 556 | "1065": { 557 | "id": "1065", 558 | "name": "祖传药丸", 559 | "description": "功能不明", 560 | "grade": 1 561 | }, 562 | "1066": { 563 | "id": "1066", 564 | "name": "精准扶贫", 565 | "description": "家境为0时家境+1", 566 | "condition": "(MNY<1)&(MNY>-1)", 567 | "grade": 0, 568 | "effect": { 569 | "MNY": 1 570 | } 571 | }, 572 | "1067": { 573 | "id": "1067", 574 | "name": "乐天派", 575 | "description": "快乐为0时快乐+1", 576 | "condition": "(SPR<1)&(SPR>-1)", 577 | "grade": 1, 578 | "effect": { 579 | "SPR": 1 580 | } 581 | }, 582 | "1068": { 583 | "id": "1068", 584 | "name": "命悬一线", 585 | "description": "体质为0时体质+1", 586 | "condition": "(STR<1)&(STR>-1)", 587 | "grade": 0, 588 | "effect": { 589 | "STR": 1 590 | } 591 | }, 592 | "1069": { 593 | "id": "1069", 594 | "name": "智可生财", 595 | "description": "若20岁时智力>8,家境+2", 596 | "condition": "(AGE?[20])&(INT>8)", 597 | "grade": 0, 598 | "effect": { 599 | "MNY": 2 600 | } 601 | }, 602 | "1070": { 603 | "id": "1070", 604 | "name": "舔狗甚多", 605 | "description": "若20岁时颜值>8,快乐+2", 606 | "condition": "(AGE?[20])&(CHR>8)", 607 | "grade": 0, 608 | "effect": { 609 | "SPR": 2 610 | } 611 | }, 612 | "1071": { 613 | "id": "1071", 614 | "name": "保胎丸", 615 | "description": "你不会胎死腹中", 616 | "grade": 0 617 | }, 618 | "1072": { 619 | "id": "1072", 620 | "name": "白化病", 621 | "description": "你不会遭遇枪击", 622 | "grade": 0 623 | }, 624 | "1073": { 625 | "id": "1073", 626 | "name": "佛宗", 627 | "description": "考上哈佛大学的几率提高", 628 | "grade": 0 629 | }, 630 | "1074": { 631 | "id": "1074", 632 | "name": "悟道", 633 | "description": "智力>10时快乐+3", 634 | "condition": "INT>10", 635 | "grade": 1, 636 | "effect": { 637 | "SPR": 3 638 | } 639 | }, 640 | "1075": { 641 | "id": "1075", 642 | "name": "驻颜", 643 | "description": "体质>10时颜值+3", 644 | "condition": "STR>10", 645 | "grade": 0, 646 | "effect": { 647 | "CHR": 3 648 | } 649 | }, 650 | "1076": { 651 | "id": "1076", 652 | "name": "界限突破", 653 | "description": "体质>10时快乐+3", 654 | "condition": "STR>10", 655 | "grade": 1, 656 | "effect": { 657 | "SPR": 3 658 | } 659 | }, 660 | "1077": { 661 | "id": "1077", 662 | "name": "倾城", 663 | "description": "颜值>10时快乐+3", 664 | "condition": "CHR>10", 665 | "grade": 1, 666 | "effect": { 667 | "SPR": 3 668 | } 669 | }, 670 | "1078": { 671 | "id": "1078", 672 | "name": "训练有方", 673 | "description": "智力>10时体质+3", 674 | "condition": "INT>10", 675 | "grade": 0, 676 | "effect": { 677 | "STR": 3 678 | } 679 | }, 680 | "1079": { 681 | "id": "1079", 682 | "name": "相由心生", 683 | "description": "智力>10时颜值+3", 684 | "condition": "INT>10", 685 | "grade": 0, 686 | "effect": { 687 | "CHR": 3 688 | } 689 | }, 690 | "1080": { 691 | "id": "1080", 692 | "name": "智多鑫", 693 | "description": "智力>10时家境+3", 694 | "condition": "INT>10", 695 | "grade": 0, 696 | "effect": { 697 | "MNY": 3 698 | } 699 | }, 700 | "1081": { 701 | "id": "1081", 702 | "name": "灵光", 703 | "description": "快乐>10时其他属性+1", 704 | "condition": "SPR>10", 705 | "grade": 0, 706 | "effect": { 707 | "MNY": 1, 708 | "CHR": 1, 709 | "STR": 1, 710 | "INT": 1 711 | } 712 | }, 713 | "1082": { 714 | "id": "1082", 715 | "name": "天启", 716 | "description": "快乐>10时其他属性+2", 717 | "condition": "SPR>10", 718 | "grade": 1, 719 | "effect": { 720 | "MNY": 2, 721 | "CHR": 2, 722 | "STR": 2, 723 | "INT": 2 724 | } 725 | }, 726 | "1083": { 727 | "id": "1083", 728 | "name": "神谕", 729 | "description": "快乐>10时其他属性+3", 730 | "condition": "SPR>10", 731 | "grade": 2, 732 | "effect": { 733 | "MNY": 3, 734 | "CHR": 3, 735 | "STR": 3, 736 | "INT": 3 737 | } 738 | }, 739 | "1084": { 740 | "id": "1084", 741 | "name": "献祭", 742 | "description": "初始可用属性点-2,快乐+2", 743 | "grade": 0, 744 | "status": -2, 745 | "effect": { 746 | "SPR": 2 747 | }, 748 | "exclusive": [ 749 | "1122" 750 | ] 751 | }, 752 | "1085": { 753 | "id": "1085", 754 | "name": "幸运儿", 755 | "description": "初始可用属性点-3,快乐+5", 756 | "grade": 1, 757 | "status": -3, 758 | "effect": { 759 | "SPR": 5 760 | }, 761 | "exclusive": [ 762 | "1122" 763 | ] 764 | }, 765 | "1086": { 766 | "id": "1086", 767 | "name": "挑战者", 768 | "description": "初始可用点-10", 769 | "grade": 0, 770 | "status": -10, 771 | "exclusive": [ 772 | "1122" 773 | ] 774 | }, 775 | "1087": { 776 | "id": "1087", 777 | "name": "你不懂", 778 | "description": "家境>10时快乐+3", 779 | "condition": "MNY>10", 780 | "grade": 1, 781 | "effect": { 782 | "SPR": 3 783 | } 784 | }, 785 | "1088": { 786 | "id": "1088", 787 | "name": "整容", 788 | "description": "家境>10时颜值+3", 789 | "condition": "MNY>10", 790 | "grade": 0, 791 | "effect": { 792 | "CHR": 3 793 | } 794 | }, 795 | "1089": { 796 | "id": "1089", 797 | "name": "钻石健身卡", 798 | "description": "家境>10时体质+3", 799 | "condition": "MNY>10", 800 | "grade": 0, 801 | "effect": { 802 | "STR": 3 803 | } 804 | }, 805 | "1090": { 806 | "id": "1090", 807 | "name": "身残志坚", 808 | "description": "体质<0时其他属性+1", 809 | "condition": "STR<0", 810 | "grade": 0, 811 | "effect": { 812 | "SPR": 1, 813 | "MNY": 1, 814 | "CHR": 1, 815 | "INT": 1 816 | } 817 | }, 818 | "1091": { 819 | "id": "1091", 820 | "name": "活死人", 821 | "description": "体质<-1时其他属性+2", 822 | "condition": "STR<0", 823 | "grade": 1, 824 | "effect": { 825 | "SPR": 2, 826 | "MNY": 2, 827 | "CHR": 2, 828 | "INT": 2 829 | } 830 | }, 831 | "1092": { 832 | "id": "1092", 833 | "name": "开一扇窗", 834 | "description": "颜值<0时其他属性+1", 835 | "condition": "CHR<0", 836 | "grade": 0, 837 | "effect": { 838 | "SPR": 1, 839 | "MNY": 1, 840 | "STR": 1, 841 | "INT": 1 842 | } 843 | }, 844 | "1093": { 845 | "id": "1093", 846 | "name": "大额头", 847 | "description": "颜值-2,智力+2", 848 | "grade": 0, 849 | "effect": { 850 | "CHR": -2, 851 | "INT": 2 852 | } 853 | }, 854 | "1094": { 855 | "id": "1094", 856 | "name": "痘痘脸", 857 | "description": "颜值-1", 858 | "grade": 0, 859 | "effect": { 860 | "CHR": -1 861 | } 862 | }, 863 | "1095": { 864 | "id": "1095", 865 | "name": "潜能", 866 | "description": "家境<0时其他属性+1", 867 | "condition": "MNY<0", 868 | "grade": 0, 869 | "effect": { 870 | "SPR": 1, 871 | "CHR": 1, 872 | "STR": 1, 873 | "INT": 1 874 | } 875 | }, 876 | "1096": { 877 | "id": "1096", 878 | "name": "哀兵", 879 | "description": "快乐<0时其他属性+1", 880 | "condition": "SPR<0", 881 | "grade": 0, 882 | "effect": { 883 | "MNY": 1, 884 | "CHR": 1, 885 | "STR": 1, 886 | "INT": 1 887 | } 888 | }, 889 | "1097": { 890 | "id": "1097", 891 | "name": "苦痛侍僧", 892 | "description": "快乐<-1时其他属性+2", 893 | "condition": "SPR<-1", 894 | "grade": 1, 895 | "effect": { 896 | "MNY": 2, 897 | "CHR": 2, 898 | "STR": 2, 899 | "INT": 2 900 | } 901 | }, 902 | "1098": { 903 | "id": "1098", 904 | "name": "觉醒", 905 | "description": "家境<-1时其他属性+2", 906 | "condition": "MNY<-1", 907 | "grade": 1, 908 | "effect": { 909 | "SPR": 2, 910 | "CHR": 2, 911 | "STR": 2, 912 | "INT": 2 913 | } 914 | }, 915 | "1099": { 916 | "id": "1099", 917 | "name": "抖M", 918 | "description": "家境-2,快乐+2", 919 | "grade": 0, 920 | "effect": { 921 | "SPR": 2, 922 | "MNY": -2 923 | } 924 | }, 925 | "1100": { 926 | "id": "1100", 927 | "name": "海的女儿", 928 | "description": "颜值-2,初始可用属性点+1", 929 | "grade": 0, 930 | "status": 1, 931 | "effect": { 932 | "CHR": -2 933 | } 934 | }, 935 | "1101": { 936 | "id": "1101", 937 | "name": "进阶", 938 | "description": "所有属性>5时,所有属性+1", 939 | "condition": "(SPR>5)&(MNY>5)&(CHR>5)&(STR>5)&(INT>5)", 940 | "grade": 0, 941 | "effect": { 942 | "SPR": 1, 943 | "MNY": 1, 944 | "CHR": 1, 945 | "STR": 1, 946 | "INT": 1 947 | } 948 | }, 949 | "1102": { 950 | "id": "1102", 951 | "name": "超进化", 952 | "description": "所有属性>5时,所有属性+2", 953 | "condition": "(SPR>5)&(MNY>5)&(CHR>5)&(STR>5)&(INT>5)", 954 | "grade": 1, 955 | "effect": { 956 | "SPR": 2, 957 | "MNY": 2, 958 | "CHR": 2, 959 | "STR": 2, 960 | "INT": 2 961 | } 962 | }, 963 | "1103": { 964 | "id": "1103", 965 | "name": "白色胶囊", 966 | "description": "你10岁时无事发生", 967 | "condition": "AGE?[10]", 968 | "grade": 0 969 | }, 970 | "1104": { 971 | "id": "1104", 972 | "name": "紫色胶囊", 973 | "description": "跳过你的40~50岁", 974 | "condition": "AGE?[40,41,42,43,44,45,46,47,48,49,50]", 975 | "grade": 2 976 | }, 977 | "1105": { 978 | "id": "1105", 979 | "name": "蓝色胶囊", 980 | "description": "你20、30岁时无事发生", 981 | "condition": "AGE?[20,30]", 982 | "grade": 1 983 | }, 984 | "1106": { 985 | "id": "1106", 986 | "name": "健康饮食", 987 | "description": "你不吃洋快餐", 988 | "grade": 0 989 | }, 990 | "1107": { 991 | "id": "1107", 992 | "name": "不想罢了", 993 | "description": "你不会上清华大学", 994 | "grade": 0 995 | }, 996 | "1108": { 997 | "id": "1108", 998 | "name": "挑衅", 999 | "description": "你喜欢没事找事", 1000 | "grade": 0 1001 | }, 1002 | "1109": { 1003 | "id": "1109", 1004 | "name": "旅行者", 1005 | "description": "你喜欢旅游", 1006 | "grade": 0 1007 | }, 1008 | "1110": { 1009 | "id": "1110", 1010 | "name": "水仙", 1011 | "description": "你比较自恋", 1012 | "grade": 0 1013 | }, 1014 | "1111": { 1015 | "id": "1111", 1016 | "name": "缺一门", 1017 | "description": "无效果", 1018 | "grade": 0 1019 | }, 1020 | "1112": { 1021 | "id": "1112", 1022 | "name": "异界来客", 1023 | "description": "你可能听到一些绝密消息", 1024 | "grade": 2 1025 | }, 1026 | "1113": { 1027 | "id": "1113", 1028 | "name": "三胎人生", 1029 | "description": "你尽可能生三胎", 1030 | "grade": 1, 1031 | "exclusive": [ 1032 | "1003", 1033 | "1025", 1034 | "1026", 1035 | 1027, 1036 | 1041, 1037 | 1046 1038 | ] 1039 | }, 1040 | "1114": { 1041 | "id": "1114", 1042 | "name": "橙色胶囊", 1043 | "description": "跳过你的60~90岁", 1044 | "condition": "AGE?[60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90]", 1045 | "grade": 3 1046 | }, 1047 | "1115": { 1048 | "id": "1115", 1049 | "name": "宙斯", 1050 | "description": "参加奥赛的几率提高", 1051 | "grade": 0 1052 | }, 1053 | "1116": { 1054 | "id": "1116", 1055 | "name": "为人民服务", 1056 | "description": "考公务员时一定能考上", 1057 | "grade": 0 1058 | }, 1059 | "1117": { 1060 | "id": "1117", 1061 | "name": "表现良好", 1062 | "description": "入狱会减刑", 1063 | "grade": 0 1064 | }, 1065 | "1118": { 1066 | "id": "1118", 1067 | "name": "小吉", 1068 | "description": "运气稍微提升", 1069 | "grade": 0 1070 | }, 1071 | "1119": { 1072 | "id": "1119", 1073 | "name": "天秤座", 1074 | "description": "据说做事很公平", 1075 | "grade": 0 1076 | }, 1077 | "1120": { 1078 | "id": "1120", 1079 | "name": "万里挑一", 1080 | "description": "你很攻", 1081 | "grade": 0 1082 | }, 1083 | "1121": { 1084 | "id": "1121", 1085 | "name": "把握不住", 1086 | "description": "你有强迫症", 1087 | "grade": 0 1088 | }, 1089 | "1122": { 1090 | "id": "1122", 1091 | "name": "急了急了", 1092 | "description": "赶着投胎,不要初始属性了", 1093 | "grade": 1, 1094 | "status": -20, 1095 | "exclusive": [ 1096 | "1084", 1097 | "1085", 1098 | "1086" 1099 | ] 1100 | }, 1101 | "1123": { 1102 | "id": "1123", 1103 | "name": "不离不弃", 1104 | "description": "你不会离婚", 1105 | "grade": 0 1106 | }, 1107 | "1124": { 1108 | "id": "1124", 1109 | "name": "足量", 1110 | "description": "身高不矮", 1111 | "grade": 0 1112 | }, 1113 | "1125": { 1114 | "id": "1125", 1115 | "name": "易胖体质", 1116 | "description": "颜值更容易降低", 1117 | "grade": 0 1118 | }, 1119 | "1126": { 1120 | "id": "1126", 1121 | "name": "黄帝", 1122 | "description": "种族主义者", 1123 | "grade": 0 1124 | }, 1125 | "1127": { 1126 | "id": "1127", 1127 | "name": "左撇子", 1128 | "description": "习惯使用左手", 1129 | "grade": 0 1130 | }, 1131 | "1128": { 1132 | "id": "1128", 1133 | "name": "克苏鲁", 1134 | "description": "&▓▓▓◆▓▓▓¥#▓@■.◆", 1135 | "grade": 2 1136 | }, 1137 | "1129": { 1138 | "id": "1129", 1139 | "name": "不连续存在", 1140 | "description": "你还拥有其他人格", 1141 | "grade": 2 1142 | }, 1143 | "1130": { 1144 | "id": "1130", 1145 | "name": "占位符", 1146 | "description": "少一个可选天赋", 1147 | "grade": 0 1148 | }, 1149 | "1131": { 1150 | "id": "1131", 1151 | "name": "魔法棒", 1152 | "description": "不知道有什么用……", 1153 | "grade": 2 1154 | }, 1155 | "1132": { 1156 | "id": 1132, 1157 | "name": "返老还童", 1158 | "description": "可能会回到年轻的时候", 1159 | "grade": 2 1160 | }, 1161 | "1133": { 1162 | "id": 1133, 1163 | "name": "时光倒流", 1164 | "description": "或许时间会倒流", 1165 | "grade": 1 1166 | }, 1167 | "1134": { 1168 | "id": 1134, 1169 | "name": "转世重修", 1170 | "description": "渡劫失败重生", 1171 | "grade": 3 1172 | } 1173 | } --------------------------------------------------------------------------------