├── .gitignore
├── README.md
├── craco.config.js
├── package.json
├── public
├── card-v1.png
├── card-v2.png
├── icon.png
├── icon192.png
├── icon@2x.png
├── index.html
├── manifest.json
└── robots.txt
├── src
├── App.js
├── data.js
├── index.css
├── index.js
├── logo.svg
├── serviceWorker.js
├── setupTests.js
└── store.js
├── tailwind.config.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | .now
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Original project: https://github.com/HackPlan/mafan
--------------------------------------------------------------------------------
/craco.config.js:
--------------------------------------------------------------------------------
1 | // Override the CRA config
2 | // Doc: https://github.com/sharegate/craco/blob/master/packages/craco/README.md#configuration-overview
3 | module.exports = function ({ env }) {
4 | return {
5 | eslint: {
6 | configure: {
7 | rules: {
8 | },
9 | },
10 | },
11 | babel: {
12 | plugins: [
13 | '@babel/plugin-proposal-optional-chaining',
14 | ["@babel/plugin-proposal-decorators", { "legacy": true }],
15 | ],
16 | },
17 | style: {
18 | postcss: {
19 | mode: 'extends',
20 | plugins: [
21 | require('postcss-import'),
22 | require('tailwindcss')('./tailwind.config.js'),
23 | require('postcss-nested'),
24 | require('autoprefixer'),
25 | require('postcss-preset-env')({ stage: 1 }),
26 | ],
27 | },
28 | },
29 | webpack: {
30 | plugins: []
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mafan2020",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@babel/plugin-proposal-decorators": "^7.8.3",
7 | "@craco/craco": "^5.6.4",
8 | "@fullhuman/postcss-purgecss": "^2.1.0",
9 | "@hackplan/uui": "^0.2.11",
10 | "@testing-library/jest-dom": "^4.2.4",
11 | "@testing-library/react": "^9.3.2",
12 | "@testing-library/user-event": "^7.1.2",
13 | "autoprefixer": "^9.7.5",
14 | "mobx": "^5.15.4",
15 | "mobx-persist": "^0.4.1",
16 | "mobx-react-lite": "^1.5.2",
17 | "postcss-import": "^12.0.1",
18 | "postcss-nested": "^4.2.1",
19 | "postcss-preset-env": "^6.7.0",
20 | "react": "^16.13.1",
21 | "react-dom": "^16.13.1",
22 | "react-scripts": "3.4.1",
23 | "tailwindcss": "^1.2.0"
24 | },
25 | "scripts": {
26 | "start": "craco start",
27 | "build": "craco build",
28 | "test": "craco test"
29 | },
30 | "eslintConfig": {
31 | "extends": "react-app"
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/card-v1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/untsop/mafan-2020/ee81ca1d47406d5500bded12fe8e003f88c995a7/public/card-v1.png
--------------------------------------------------------------------------------
/public/card-v2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/untsop/mafan-2020/ee81ca1d47406d5500bded12fe8e003f88c995a7/public/card-v2.png
--------------------------------------------------------------------------------
/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/untsop/mafan-2020/ee81ca1d47406d5500bded12fe8e003f88c995a7/public/icon.png
--------------------------------------------------------------------------------
/public/icon192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/untsop/mafan-2020/ee81ca1d47406d5500bded12fe8e003f88c995a7/public/icon192.png
--------------------------------------------------------------------------------
/public/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/untsop/mafan-2020/ee81ca1d47406d5500bded12fe8e003f88c995a7/public/icon@2x.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
22 |
23 |
32 | 麻烦
33 |
34 |
35 |
36 |
37 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "麻烦",
3 | "name": "麻烦",
4 | "icons": [
5 | {
6 | "src": "icon192.png",
7 | "type": "image/png",
8 | "sizes": "192x192"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#ffffff",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useReducer, useEffect, useState } from 'react';
2 | import { observer } from "mobx-react-lite";
3 | import { gameContext, problems, levels } from "./store";
4 | import { Button, Tag } from '@hackplan/uui';
5 | import logo from './logo.svg';
6 | import './index.css';
7 |
8 | const App = observer(() => {
9 | const gameStore = useContext(gameContext);
10 | const level = gameStore.useLevel()
11 | const goods = gameStore.useGoods()
12 | const defaultStates = Object.fromEntries(problems.map((p) => [p.title, 0]))
13 |
14 | const [shared, setShared] = useState(false)
15 | const counterReducer = (state, action) => {
16 | switch (action.type) {
17 | case 'increment':
18 | return { ...state, [action.title]: state[action.title] + 1 };
19 | case 'debug':
20 | return { ...state, [action.title]: state[action.title] + 10 };
21 | case 'reset':
22 | return { ...state, [action.title]: 0 };
23 | default:
24 | throw new Error();
25 | }
26 | }
27 |
28 | const [problemsStates, dispatch] = useReducer(counterReducer, defaultStates)
29 |
30 | useEffect(() => {
31 | for (const title in problemsStates) {
32 | if (problemsStates[title] > 0) {
33 | const problem = problems.find((p)=> p.title === title)
34 | const targetTime = problem.time * 2
35 | problem.percent = (problemsStates[problem.title] / targetTime).toFixed(4);
36 | problem.percentage = Math.min((Math.floor((problem.percent || 0) * 10000) / 100), 100).toFixed(1) + "%"
37 | if (problemsStates[title] >= targetTime) {
38 | resetProblemTimer(problem)
39 | gameStore.solve(problem)
40 | }
41 | }
42 | }
43 | }, [problemsStates]);
44 |
45 | const resetProblemTimer = (problem) => {
46 | clearInterval(problem.counting);
47 | problem.percent = 0
48 | problem.percentage = 0
49 | dispatch({
50 | title: problem.title,
51 | type: 'reset'
52 | })
53 | }
54 |
55 | const shareOnTwitter = () => {
56 | setShared(true)
57 | window.open("https://twitter.com/intent/tweet?tw_p=tweetbutton&text=我正在玩麻烦这个游戏:https://mafan.qqsun.xyz/ by @QQSun", "_blank");
58 | }
59 |
60 | const notSolvable = (problem) => {
61 | if (problem.take && !problem.take.every((item) => {
62 | if (item.name === '你拥有的一切') {
63 | return true
64 | }
65 | const stuff = goods.find((g) => g.name === item.name)
66 | if (stuff && (stuff.number || 1) >= (item.number || 1)) {
67 | return true
68 | }
69 | return false
70 | })){
71 | return true
72 | }
73 | return false
74 | }
75 |
76 | const fixProblem = (problem) => {
77 | if (problemsStates[problem.title] > 0) {
78 | return
79 | }
80 | resetProblemTimer(problem)
81 |
82 | problem.counting = setInterval(() => {
83 | if (false) {
84 | dispatch({
85 | title: problem.title,
86 | type: 'debug'
87 | })
88 | } else{
89 | dispatch({
90 | title: problem.title,
91 | type: 'increment'
92 | })
93 | }
94 | }, 50)
95 | }
96 |
97 | return
98 |
99 |

100 |
101 | 你是 {levels.find((l) => l.level === (level || 0) )['name']}
102 |
103 |
104 |
你拥有
105 | {goods.map((stuff) => {
106 | return
107 | {stuff.name}
108 | {stuff.number && × {stuff.number}}
109 |
110 | })}
111 | {goods.length === 0 &&
一无所有
}
112 |
113 |
114 |
115 |
你的麻烦
116 | {problems.filter((problem) => {
117 | if (!problem.level.includes(level)) {
118 | return false
119 | }
120 | if (problem.dismiss && problem.dismiss.some((item) => goods.some((g) => g.name === item) )) {
121 | return false
122 | }
123 | if (problem.require && !problem.require.some((item) => goods.some((g) => g.name === item))) {
124 | return false
125 | }
126 | return true
127 | }).sort((a, b) => (b.growth ? 1 : 0) - (a.growth ? 1 : 0)).map((problem) => {
128 | return
129 |
130 |
131 |
{problem.title}
132 | {problem.take && problem.take.map((item) =>
- {item.name} {item.number && × {item.number}} )}
133 | {problem.gain && problem.gain.map((item) =>
+ {item.name} {item.number && × {item.number}} )}
134 |
135 |
136 |
137 |
138 |
139 |
142 |
143 | })}
144 |
145 | {!shared &&
146 |
147 |
148 |
将麻烦分享到 Twitter
149 |
150 |
151 |
152 |
153 |
154 |
}
155 |
156 |
157 |
158 |
159 |
162 |
163 |
164 | })
165 |
166 | export default App;
--------------------------------------------------------------------------------
/src/data.js:
--------------------------------------------------------------------------------
1 | export const mafan = {
2 | levels: [
3 | {
4 | level: 0,
5 | name: "虚无"
6 | },
7 | {
8 | level: 1,
9 | name: "婴儿"
10 | },
11 | {
12 | level: 2,
13 | name: "小孩儿"
14 | },
15 | {
16 | level: 3,
17 | name: "青年"
18 | },
19 | {
20 | level: 4,
21 | name: "中年"
22 | },
23 | {
24 | level: 5,
25 | name: "老年"
26 | },
27 | {
28 | level: 6,
29 | name: "幽灵"
30 | },
31 | ],
32 | problems: [
33 | {
34 | level: [0],
35 | title: "什么都没有",
36 | dismiss: ["希望"],
37 | gain: [
38 | {
39 | name: "希望"
40 | }
41 | ],
42 | time: 30
43 | },
44 | {
45 | level: [0],
46 | title: "你没有生命",
47 | require: ["希望"],
48 | dismiss: ["生命"],
49 | gain: [
50 | {
51 | name: "生命"
52 | }
53 | ],
54 | growth: true,
55 | time: 30
56 | },
57 | {
58 | level: [1, 2, 3, 4],
59 | title: "你需要学习",
60 | gain: [
61 | {
62 | name: "知识"
63 | }
64 | ],
65 | time: 60
66 | },
67 | {
68 | level: [1, 2, 3, 4],
69 | title: "你需要玩耍",
70 | gain: [
71 | {
72 | name: "记忆"
73 | }
74 | ],
75 | time: 30
76 | },
77 | {
78 | level: [1],
79 | title: "你是一个婴儿",
80 | take: [
81 | {
82 | name: "记忆",
83 | number: 5
84 | }
85 | ],
86 | growth: true,
87 | time: 60
88 | },
89 | {
90 | level: [2],
91 | title: "你是一个小孩儿",
92 | take: [
93 | {
94 | name: "知识",
95 | number: 5
96 | },
97 | {
98 | name: "经历",
99 | number: 3
100 | }
101 | ],
102 | growth: true,
103 | time: 60
104 | },
105 | {
106 | level: [2, 3, 4],
107 | title: "你需要朋友",
108 | gain: [
109 | {
110 | name: "朋友"
111 | }
112 | ],
113 | time: 20
114 | },
115 | {
116 | level: [2, 3, 4],
117 | title: "你厌倦了朋友",
118 | require: ["朋友"],
119 | take: [
120 | {
121 | name: "朋友"
122 | }
123 | ],
124 | gain: [
125 | {
126 | name: "经历"
127 | }
128 | ],
129 | time: 10
130 | },
131 | {
132 | level: [3],
133 | title: "你是一个年轻人",
134 | take: [
135 | {
136 | name: "经历",
137 | number: 5
138 | },
139 | {
140 | name: "金钱",
141 | number: 5
142 | },
143 | {
144 | name: "破碎的心",
145 | number: 2
146 | }
147 | ],
148 | growth: true,
149 | time: 60
150 | },
151 | {
152 | level: [3, 4],
153 | title: "你需要爱人",
154 | dismiss: ["爱人",
155 | "家庭"],
156 | gain: [
157 | {
158 | name: "爱人"
159 | }
160 | ],
161 | time: 40
162 | },
163 | {
164 | level: [3, 4],
165 | title: "爱人离开了你",
166 | require: ["爱人"],
167 | take: [
168 | {
169 | name: "爱人"
170 | }
171 | ],
172 | gain: [
173 | {
174 | name: "破碎的心"
175 | }
176 | ],
177 | time: 20
178 | },
179 | {
180 | level: [3, 4],
181 | title: "你需要工作",
182 | gain: [
183 | {
184 | name: "压力"
185 | },
186 | {
187 | name: "金钱"
188 | }
189 | ],
190 | time: 30
191 | },
192 | {
193 | level: [3, 4, 5],
194 | title: "你需要放松",
195 | require: ["压力"],
196 | take: [
197 | {
198 | name: "压力"
199 | }
200 | ],
201 | time: 30
202 | },
203 | {
204 | level: [4],
205 | title: "你是一个中年人",
206 | take: [
207 | {
208 | name: "经历",
209 | number: 5
210 | },
211 | {
212 | name: "东西",
213 | number: 5
214 | },
215 | {
216 | name: "朋友",
217 | number: 5
218 | }
219 | ],
220 | growth: true,
221 | time: 60
222 | },
223 | {
224 | level: [4],
225 | title: "你需要结婚成家",
226 | require: ["爱人"],
227 | dismiss: ["家庭"],
228 | gain: [
229 | {
230 | name: "家庭"
231 | }
232 | ],
233 | time: 60
234 | },
235 | {
236 | level: [3, 4],
237 | title: "你需要换一个更好的工作",
238 | take: [
239 | {
240 | name: "知识",
241 | number: 2
242 | }
243 | ],
244 | gain: [
245 | {
246 | name: "经历",
247 | number: 2
248 | },
249 | {
250 | name: "金钱",
251 | number: 3
252 | }
253 | ],
254 | time: 60
255 | },
256 | {
257 | level: [3, 4],
258 | title: "你需要买更多东西",
259 | take: [
260 | {
261 | name: "金钱"
262 | }
263 | ],
264 | gain: [
265 | {
266 | name: "东西"
267 | }
268 | ],
269 | time: 20
270 | },
271 | {
272 | level: [5],
273 | title: "你疾病缠身",
274 | take: [
275 | {
276 | name: "金钱",
277 | number: 3
278 | }
279 | ],
280 | time: 60
281 | },
282 | {
283 | level: [5],
284 | title: "你感到孤独",
285 | take: [
286 | {
287 | name: "朋友",
288 | number: 2
289 | }
290 | ],
291 | time: 60
292 | },
293 | {
294 | level: [5],
295 | title: "你感到无聊",
296 | take: [
297 | {
298 | name: "记忆",
299 | number: 2
300 | }
301 | ],
302 | time: 60
303 | },
304 | {
305 | level: [5],
306 | title: "你在逐渐死去",
307 | take: [
308 | {
309 | name: "希望"
310 | },
311 | {
312 | name: "生命"
313 | }
314 | ],
315 | growth: true,
316 | time: 600
317 | },
318 | {
319 | level: [6],
320 | title: "你死了",
321 | take: [
322 | {
323 | name: "你拥有的一切"
324 | }
325 | ],
326 | growth: true,
327 | reset: true,
328 | time: 600
329 | }
330 | ]
331 | }
332 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import '@hackplan/uui/lib/index.css';
2 |
3 | @import 'tailwindcss/base';
4 | @import 'tailwindcss/components';
5 | @import 'tailwindcss/utilities';
6 |
7 | body {
8 | margin: 0;
9 | width: 100%;
10 | background: #f4f4f4;
11 | }
12 |
13 | img, svg {
14 | display: inline;
15 | vertical-align: baseline;
16 | }
17 |
18 | button:focus, div:focus, :focus {
19 | outline: none !important;
20 | }
21 |
22 | .UUI-Tag-Root .UUI-Tag-Content{
23 | font-variant-numeric: tabular-nums;
24 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import * as serviceWorker from './serviceWorker';
5 | import { create as createMobxPersit } from 'mobx-persist';
6 | import { gameStore } from './store';
7 |
8 | const hydrate = createMobxPersit({
9 | jsonify: true,
10 | })
11 |
12 | async function main() {
13 | try {
14 | await hydrate("mafan", gameStore)
15 | } catch (e) {
16 | localStorage.clear();
17 | }
18 |
19 | ReactDOM.render(
20 |
21 |
22 | ,
23 | document.getElementById('root')
24 | );
25 |
26 | // If you want your app to work offline and load faster, you can change
27 | // unregister() to register() below. Note this comes with some pitfalls.
28 | // Learn more about service workers: https://bit.ly/CRA-PWA
29 | }
30 |
31 | void main().catch((e) => console.error(e));
32 |
33 | serviceWorker.register();
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 | import { action, observable } from 'mobx';
3 | import { persist } from 'mobx-persist';
4 | import { useObserver } from 'mobx-react-lite';
5 | import { mafan } from './data'
6 |
7 | const updateStuff = (playerGoods, s, isTake) => {
8 | let number = 0;
9 | if (isTake) {
10 | number = -1 * (s.number || 1);
11 | } else {
12 | number = s.number || 1;
13 | }
14 |
15 | const stuffToUpdate = playerGoods.find(stuff => s.name === stuff.name)
16 |
17 | if (stuffToUpdate) {
18 | if (!stuffToUpdate.number) {
19 | stuffToUpdate.number = number + 1;
20 | } else {
21 | stuffToUpdate.number = stuffToUpdate.number + number;
22 | }
23 | if (stuffToUpdate.number === 0) {
24 | playerGoods.splice(playerGoods.findIndex(stuff => s.name === stuff.name), 1);
25 | }
26 | } else {
27 | playerGoods.push(s);
28 | }
29 | }
30 |
31 | const updateGoods = (player, problem) => {
32 | if (problem.gain) {
33 | problem.gain.map((i) => updateStuff(player, i))
34 | }
35 | if (problem.take) {
36 | problem.take.map((i) => updateStuff(player, i, true))
37 | }
38 | }
39 |
40 | export class DataStore {
41 | @persist @observable level = 0
42 | @persist('list') @observable goods = []
43 |
44 | @action.bound
45 | solve(problem) {
46 | if (problem.dismiss) {
47 | if (problem.dismiss.some((item) => this.goods.some((g) => g.name === item))) {
48 | return
49 | }
50 | }
51 |
52 | if (problem.take) {
53 | if (problem.take.some((stuff) => {
54 | if (stuff.name === '你拥有的一切') {
55 | return false
56 | }
57 | const foundItem = this.goods.find((item) => item.name === stuff.name)
58 | if (foundItem && (foundItem.number || 1) >= (stuff.number || 1)) {
59 | return false
60 | }
61 | return true
62 | })) {
63 | return
64 | }
65 | }
66 |
67 | if (problem.reset) {
68 | this.level = 0;
69 | this.goods = [];
70 | return true;
71 | }
72 | updateGoods(this.goods, problem);
73 | if (problem.growth) {
74 | this.level += 1;
75 | }
76 | }
77 |
78 | useLevel = () => useObserver(() => this.level)
79 | useGoods = () => useObserver(() => this.goods)
80 | }
81 |
82 | export const gameStore = new DataStore()
83 | export const problems = mafan.problems
84 | export const levels = mafan.levels
85 |
86 | export const gameContext = createContext(gameStore);
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | prefix: '',
4 | important: false,
5 | separator: ':',
6 | theme: {
7 | screens: {
8 | sm: '640px',
9 | md: '768px',
10 | lg: '1024px',
11 | xl: '1280px',
12 | },
13 | colors: {
14 | brand: '#fbb778',
15 | 'brand-dark': '#bf6315',
16 | 'fafafa': '#fafafa',
17 | 'f4f4f4': '#f4f4f4',
18 | 'f0f0f0': '#f0f0f0',
19 | 'e6e6e6': '#e6e6e6',
20 | '333333': '#333333',
21 | 'eeeeee': '#eeeeee',
22 | 'dddddd': '#dddddd',
23 | 'c1c1c1': '#C1C1C1',
24 | '363636': '#363636',
25 | '141414': '#141414',
26 | '272727': '#272727',
27 | '575757': '#575757',
28 | '4a4a4a': '#4A4A4A',
29 | '5a5a5a': '#5a5a5a',
30 | '2d2d2d': '#2d2d2d',
31 | '424242': '#424242',
32 | '888888': '#888888',
33 | '999999': '#999999',
34 | transparent: 'transparent',
35 |
36 | black: '#000',
37 | white: '#fff',
38 |
39 | gray: {
40 | 100: '#f7fafc',
41 | 200: '#edf2f7',
42 | 300: '#e2e8f0',
43 | 400: '#cbd5e0',
44 | 500: '#a0aec0',
45 | 600: '#718096',
46 | 700: '#4a5568',
47 | 800: '#2d3748',
48 | 900: '#1a202c',
49 | },
50 | red: {
51 | 100: '#fff5f5',
52 | 200: '#fed7d7',
53 | 300: '#feb2b2',
54 | 400: '#fc8181',
55 | 500: '#f56565',
56 | 600: '#e53e3e',
57 | 700: '#c53030',
58 | 800: '#9b2c2c',
59 | 900: '#742a2a',
60 | },
61 | orange: {
62 | 100: '#fffaf0',
63 | 200: '#feebc8',
64 | 300: '#fbd38d',
65 | 400: '#f6ad55',
66 | 500: '#ed8936',
67 | 600: '#dd6b20',
68 | 700: '#c05621',
69 | 800: '#9c4221',
70 | 900: '#7b341e',
71 | },
72 | yellow: {
73 | 100: '#fffff0',
74 | 200: '#fefcbf',
75 | 300: '#faf089',
76 | 400: '#f6e05e',
77 | 500: '#ecc94b',
78 | 600: '#d69e2e',
79 | 700: '#b7791f',
80 | 800: '#975a16',
81 | 900: '#744210',
82 | },
83 | green: {
84 | 100: '#f0fff4',
85 | 200: '#c6f6d5',
86 | 300: '#9ae6b4',
87 | 400: '#68d391',
88 | 500: '#48bb78',
89 | 600: '#38a169',
90 | 700: '#2f855a',
91 | 800: '#276749',
92 | 900: '#22543d',
93 | },
94 | teal: {
95 | 100: '#e6fffa',
96 | 200: '#b2f5ea',
97 | 300: '#81e6d9',
98 | 400: '#4fd1c5',
99 | 500: '#38b2ac',
100 | 600: '#319795',
101 | 700: '#2c7a7b',
102 | 800: '#285e61',
103 | 900: '#234e52',
104 | },
105 | blue: {
106 | 100: '#ebf8ff',
107 | 200: '#bee3f8',
108 | 300: '#90cdf4',
109 | 400: '#63b3ed',
110 | 500: '#4299e1',
111 | 600: '#3182ce',
112 | 700: '#2b6cb0',
113 | 800: '#2c5282',
114 | 900: '#2a4365',
115 | },
116 | indigo: {
117 | 100: '#ebf4ff',
118 | 200: '#c3dafe',
119 | 300: '#a3bffa',
120 | 400: '#7f9cf5',
121 | 500: '#667eea',
122 | 600: '#5a67d8',
123 | 700: '#4c51bf',
124 | 800: '#434190',
125 | 900: '#3c366b',
126 | },
127 | purple: {
128 | 100: '#faf5ff',
129 | 200: '#e9d8fd',
130 | 300: '#d6bcfa',
131 | 400: '#b794f4',
132 | 500: '#9f7aea',
133 | 600: '#805ad5',
134 | 700: '#6b46c1',
135 | 800: '#553c9a',
136 | 900: '#44337a',
137 | },
138 | pink: {
139 | 100: '#fff5f7',
140 | 200: '#fed7e2',
141 | 300: '#fbb6ce',
142 | 400: '#f687b3',
143 | 500: '#ed64a6',
144 | 600: '#d53f8c',
145 | 700: '#b83280',
146 | 800: '#97266d',
147 | 900: '#702459',
148 | },
149 | },
150 | spacing: {
151 | px: '1px',
152 | '0': '0',
153 | '1': '0.25rem',
154 | '2': '0.5rem',
155 | '3': '0.75rem',
156 | '4': '1rem',
157 | '5': '1.25rem',
158 | '6': '1.5rem',
159 | '8': '2rem',
160 | '10': '2.5rem',
161 | '12': '3rem',
162 | '16': '4rem',
163 | '20': '5rem',
164 | '24': '6rem',
165 | '32': '8rem',
166 | '40': '10rem',
167 | '48': '12rem',
168 | '56': '14rem',
169 | '64': '16rem',
170 | },
171 | backgroundColor: theme => theme('colors'),
172 | backgroundPosition: {
173 | bottom: 'bottom',
174 | center: 'center',
175 | left: 'left',
176 | 'left-bottom': 'left bottom',
177 | 'left-top': 'left top',
178 | right: 'right',
179 | 'right-bottom': 'right bottom',
180 | 'right-top': 'right top',
181 | top: 'top',
182 | },
183 | backgroundSize: {
184 | auto: 'auto',
185 | cover: 'cover',
186 | contain: 'contain',
187 | },
188 | borderColor: theme => ({
189 | ...theme('colors'),
190 | default: theme('colors.gray.300', 'currentColor'),
191 | }),
192 | borderRadius: {
193 | none: '0',
194 | sm: '0.125rem',
195 | default: '0.25rem',
196 | lg: '0.5rem',
197 | full: '9999px',
198 | },
199 | borderWidth: {
200 | default: '1px',
201 | '0': '0',
202 | '2': '2px',
203 | '4': '4px',
204 | '8': '8px',
205 | },
206 | boxShadow: {
207 | default: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
208 | md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
209 | lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
210 | xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
211 | '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
212 | inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
213 | outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',
214 | none: 'none',
215 | },
216 | container: {},
217 | cursor: {
218 | auto: 'auto',
219 | default: 'default',
220 | pointer: 'pointer',
221 | wait: 'wait',
222 | text: 'text',
223 | move: 'move',
224 | 'not-allowed': 'not-allowed',
225 | },
226 | fill: {
227 | current: 'currentColor',
228 | },
229 | flex: {
230 | '1': '1 1 0%',
231 | auto: '1 1 auto',
232 | initial: '0 1 auto',
233 | none: 'none',
234 | },
235 | flexGrow: {
236 | '0': '0',
237 | default: '1',
238 | },
239 | flexShrink: {
240 | '0': '0',
241 | default: '1',
242 | },
243 | fontFamily: {
244 | sans: [
245 | '-apple-system',
246 | 'BlinkMacSystemFont',
247 | '"Segoe UI"',
248 | 'Roboto',
249 | '"Helvetica Neue"',
250 | 'Arial',
251 | '"Noto Sans"',
252 | 'sans-serif',
253 | '"Apple Color Emoji"',
254 | '"Segoe UI Emoji"',
255 | '"Segoe UI Symbol"',
256 | '"Noto Color Emoji"',
257 | ],
258 | serif: [
259 | 'Georgia',
260 | 'Cambria',
261 | '"Times New Roman"',
262 | 'Times',
263 | 'serif',
264 | ],
265 | mono: [
266 | 'Menlo',
267 | 'Monaco',
268 | 'Consolas',
269 | '"Liberation Mono"',
270 | '"Courier New"',
271 | 'monospace',
272 | ],
273 | },
274 | fontSize: {
275 | xs: '0.75rem',
276 | sm: '0.875rem',
277 | base: '1rem',
278 | lg: '1.125rem',
279 | xl: '1.25rem',
280 | '2xl': '1.5rem',
281 | '3xl': '1.875rem',
282 | '4xl': '2.25rem',
283 | '5xl': '3rem',
284 | '6xl': '4rem',
285 | },
286 | fontWeight: {
287 | hairline: '100',
288 | thin: '200',
289 | light: '300',
290 | normal: '400',
291 | medium: '500',
292 | semibold: '600',
293 | bold: '700',
294 | extrabold: '800',
295 | black: '900',
296 | },
297 | height: theme => ({
298 | auto: 'auto',
299 | ...theme('spacing'),
300 | full: '100%',
301 | screen: '100vh',
302 | }),
303 | inset: {
304 | '0': '0',
305 | auto: 'auto',
306 | },
307 | letterSpacing: {
308 | tighter: '-0.05em',
309 | tight: '-0.025em',
310 | normal: '0',
311 | wide: '0.025em',
312 | wider: '0.05em',
313 | widest: '0.1em',
314 | },
315 | lineHeight: {
316 | none: '1',
317 | tight: '1.25',
318 | snug: '1.375',
319 | normal: '1.5',
320 | relaxed: '1.625',
321 | loose: '2',
322 | },
323 | listStyleType: {
324 | none: 'none',
325 | disc: 'disc',
326 | decimal: 'decimal',
327 | },
328 | margin: (theme, { negative }) => ({
329 | auto: 'auto',
330 | ...theme('spacing'),
331 | ...negative(theme('spacing')),
332 | }),
333 | maxHeight: {
334 | full: '100%',
335 | screen: '100vh',
336 | },
337 | maxWidth: {
338 | xs: '20rem',
339 | sm: '24rem',
340 | md: '28rem',
341 | lg: '32rem',
342 | xl: '36rem',
343 | '2xl': '42rem',
344 | '3xl': '48rem',
345 | '4xl': '56rem',
346 | '5xl': '64rem',
347 | '6xl': '72rem',
348 | full: '100%',
349 | },
350 | minHeight: {
351 | '0': '0',
352 | full: '100%',
353 | screen: '100vh',
354 | },
355 | minWidth: {
356 | '0': '0',
357 | full: '100%',
358 | },
359 | objectPosition: {
360 | bottom: 'bottom',
361 | center: 'center',
362 | left: 'left',
363 | 'left-bottom': 'left bottom',
364 | 'left-top': 'left top',
365 | right: 'right',
366 | 'right-bottom': 'right bottom',
367 | 'right-top': 'right top',
368 | top: 'top',
369 | },
370 | opacity: {
371 | '0': '0',
372 | '25': '0.25',
373 | '50': '0.5',
374 | '75': '0.75',
375 | '100': '1',
376 | },
377 | order: {
378 | first: '-9999',
379 | last: '9999',
380 | none: '0',
381 | '1': '1',
382 | '2': '2',
383 | '3': '3',
384 | '4': '4',
385 | '5': '5',
386 | '6': '6',
387 | '7': '7',
388 | '8': '8',
389 | '9': '9',
390 | '10': '10',
391 | '11': '11',
392 | '12': '12',
393 | },
394 | padding: theme => theme('spacing'),
395 | placeholderColor: theme => theme('colors'),
396 | stroke: {
397 | current: 'currentColor',
398 | },
399 | textColor: theme => theme('colors'),
400 | width: theme => ({
401 | auto: 'auto',
402 | ...theme('spacing'),
403 | '1/2': '50%',
404 | '1/3': '33.333333%',
405 | '2/3': '66.666667%',
406 | '1/4': '25%',
407 | '2/4': '50%',
408 | '3/4': '75%',
409 | '1/5': '20%',
410 | '2/5': '40%',
411 | '3/5': '60%',
412 | '4/5': '80%',
413 | '1/6': '16.666667%',
414 | '2/6': '33.333333%',
415 | '3/6': '50%',
416 | '4/6': '66.666667%',
417 | '5/6': '83.333333%',
418 | '1/12': '8.333333%',
419 | '2/12': '16.666667%',
420 | '3/12': '25%',
421 | '4/12': '33.333333%',
422 | '5/12': '41.666667%',
423 | '6/12': '50%',
424 | '7/12': '58.333333%',
425 | '8/12': '66.666667%',
426 | '9/12': '75%',
427 | '10/12': '83.333333%',
428 | '11/12': '91.666667%',
429 | full: '100%',
430 | screen: '100vw',
431 | }),
432 | zIndex: {
433 | auto: 'auto',
434 | '0': '0',
435 | '10': '10',
436 | '20': '20',
437 | '30': '30',
438 | '40': '40',
439 | '50': '50',
440 | },
441 | },
442 | variants: {
443 | accessibility: ['responsive', 'focus'],
444 | alignContent: ['responsive'],
445 | alignItems: ['responsive'],
446 | alignSelf: ['responsive'],
447 | appearance: ['responsive'],
448 | backgroundAttachment: ['responsive'],
449 | backgroundColor: ['responsive', 'hover', 'focus', 'disabled', 'dark', 'dark-hover', 'dark-group-hover'],
450 | backgroundPosition: ['responsive'],
451 | backgroundRepeat: ['responsive'],
452 | backgroundSize: ['responsive'],
453 | borderCollapse: ['responsive'],
454 | borderColor: ['responsive', 'hover', 'focus', 'dark', 'dark-focus', 'dark-focus-within'],
455 | borderRadius: ['responsive'],
456 | borderStyle: ['responsive'],
457 | borderWidth: ['responsive', 'last'],
458 | boxShadow: ['responsive', 'hover', 'focus'],
459 | cursor: ['responsive'],
460 | display: ['responsive', 'group-hover'],
461 | fill: ['responsive'],
462 | flex: ['responsive'],
463 | flexDirection: ['responsive'],
464 | flexGrow: ['responsive'],
465 | flexShrink: ['responsive'],
466 | flexWrap: ['responsive'],
467 | float: ['responsive'],
468 | fontFamily: ['responsive'],
469 | fontSize: ['responsive'],
470 | fontSmoothing: ['responsive'],
471 | fontStyle: ['responsive'],
472 | fontWeight: ['responsive', 'hover', 'focus'],
473 | height: ['responsive', 'empty'],
474 | inset: ['responsive'],
475 | justifyContent: ['responsive'],
476 | letterSpacing: ['responsive'],
477 | lineHeight: ['responsive'],
478 | listStylePosition: ['responsive'],
479 | listStyleType: ['responsive'],
480 | margin: ['responsive'],
481 | maxHeight: ['responsive'],
482 | maxWidth: ['responsive'],
483 | minHeight: ['responsive'],
484 | minWidth: ['responsive'],
485 | objectFit: ['responsive'],
486 | objectPosition: ['responsive'],
487 | opacity: ['responsive', 'hover', 'focus'],
488 | order: ['responsive'],
489 | outline: ['responsive', 'focus'],
490 | overflow: ['responsive'],
491 | padding: ['responsive'],
492 | placeholderColor: ['responsive', 'focus'],
493 | pointerEvents: ['responsive'],
494 | position: ['responsive'],
495 | resize: ['responsive'],
496 | stroke: ['responsive'],
497 | tableLayout: ['responsive'],
498 | textAlign: ['responsive'],
499 | textColor: ['responsive', 'hover', 'focus', 'dark', 'dark-hover', 'dark-active'],
500 | textDecoration: ['responsive', 'hover', 'focus'],
501 | textTransform: ['responsive'],
502 | userSelect: ['responsive'],
503 | verticalAlign: ['responsive'],
504 | visibility: ['responsive'],
505 | whitespace: ['responsive'],
506 | width: ['responsive'],
507 | wordBreak: ['responsive'],
508 | zIndex: ['responsive'],
509 | },
510 | corePlugins: {},
511 | plugins: [
512 | function ({ addVariant, e }) {
513 | addVariant('last', ({ modifySelectors, separator }) => {
514 | modifySelectors(({ className }) => {
515 | return `.${e(`last${separator}${className}`)}:last-child`
516 | })
517 | })
518 | addVariant('empty', ({ modifySelectors, separator }) => {
519 | modifySelectors(({ className }) => {
520 | return `.${e(`empty${separator}${className}`)}:empty`
521 | })
522 | })
523 | addVariant('important', ({ container }) => {
524 | container.walkRules(rule => {
525 | rule.selector = `.\\!${rule.selector.slice(1)}`
526 | rule.walkDecls(decl => {
527 | decl.important = true
528 | })
529 | })
530 | })
531 | addVariant('disabled', ({ modifySelectors, separator }) => {
532 | modifySelectors(({ className }) => {
533 | return `.${e(`disabled${separator}${className}`)}:disabled`
534 | })
535 | })
536 | addVariant('dark', ({ modifySelectors, separator }) => {
537 | modifySelectors(({ className }) => {
538 | return `.mode-dark .${e(`dark${separator}${className}`)}`;
539 | });
540 | });
541 |
542 | addVariant('dark-hover', ({ modifySelectors, separator }) => {
543 | modifySelectors(({ className }) => {
544 | return `.mode-dark .${e(`dark-hover${separator}${className}`)}:hover`;
545 | });
546 | });
547 |
548 | addVariant('dark-focus', ({ modifySelectors, separator }) => {
549 | modifySelectors(({ className }) => {
550 | return `.mode-dark .${e(`dark-focus${separator}${className}`)}:focus`;
551 | });
552 | });
553 |
554 | addVariant('dark-active', ({ modifySelectors, separator }) => {
555 | modifySelectors(({ className }) => {
556 | return `.mode-dark .${e(`dark-active${separator}${className}`)}:active`;
557 | });
558 | });
559 |
560 | addVariant('dark-group-hover', ({ modifySelectors, separator }) => {
561 | modifySelectors(({ className }) => {
562 | return `.mode-dark .group:hover .${e(`dark-group-hover${separator}${className}`)}`;
563 | });
564 | });
565 |
566 | addVariant('dark-focus-within', ({ modifySelectors, separator }) => {
567 | modifySelectors(({ className }) => {
568 | return `.mode-dark .${e(`dark-focus-within${separator}${className}`)}:focus-within`;
569 | });
570 | });
571 | }
572 | ]
573 | }
574 |
--------------------------------------------------------------------------------