diff --git a/.stylelintrc.cjs b/.stylelintrc.cjs index 55aba0a..16bbc13 100644 --- a/.stylelintrc.cjs +++ b/.stylelintrc.cjs @@ -1,12 +1,12 @@ /** -* stylelint config -* @ref https://stylelint.io/ -* @desc generated at 9/15/2022, 12:51:58 PM by streakingman-cli@1.9.2 -*/ + * stylelint config + * @ref https://stylelint.io/ + * @desc generated at 9/15/2022, 12:51:58 PM by streakingman-cli@1.9.2 + */ module.exports = { - "extends": [ - "stylelint-config-standard-scss", - "stylelint-config-prettier-scss" - ] -} + extends: [ + 'stylelint-config-standard-scss', + 'stylelint-config-prettier-scss', + ], +}; diff --git a/index.html b/index.html index e0d1c84..278cb0a 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Vite + React + TS + 有解的羊了个羊
diff --git a/public/github.svg b/public/github.svg new file mode 100644 index 0000000..e75ce13 --- /dev/null +++ b/public/github.svg @@ -0,0 +1,5 @@ + diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/App.css b/src/App.css index ce9c0e4..6d18af5 100644 --- a/src/App.css +++ b/src/App.css @@ -1,44 +1,90 @@ #root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; text-align: center; + width: 100%; } -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; +.app { + width: 100%; + max-width: 768px; + margin: 0 auto; } -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); +.scene-container { + width: 100%; + padding-bottom: 100%; + position: relative; } -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); +.scene-inner { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; + overflow: visible; + font-size: 30px; } -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } +.symbol { + width: 12.5%; + padding-bottom: 12.5%; + position: absolute; + transition: 0.3s; + left: 0; + top: 0; } -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } +.symbol-inner { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; + border: 2px solid #444; + background-color: #eee; } -.card { - padding: 2em; +.queue-container { + border-radius: 8px; + width: 100%; + padding-bottom: 15%; + border: 2px solid gray; + margin-bottom: 16px; } -.read-the-docs { - color: #888; +.flex-container { + display: flex; + gap: 8px; +} + +.flex-center { + justify-content: center; + align-items: center; +} + +.flex-grow { + flex-grow: 1; +} + +.flex-between { + justify-content: space-between; + align-items: center; +} + +.modal { + position: fixed; + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + backdrop-filter: blur(10px); + background-color: rgb(0 0 0 / 10%); + top: 0; + left: 0; } diff --git a/src/App.tsx b/src/App.tsx index a6002ae..cb9e080 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,38 +1,333 @@ -import React, { useState } from 'react'; -import reactLogo from './assets/react.svg'; +import React, { FC, MouseEventHandler, useEffect, useState } from 'react'; + import './App.css'; +import { GithubIcon } from './GithubIcon'; +import { randomString } from './utils'; -function App() { - const [count, setCount] = useState(0); +const icons = [`🎨`, `🌈`, `⚙️`, `💻`, `📚`, `🐯`, `🐤`, `🐼`, `🐏`, `🍀`]; - return ( -
-
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

-
- ); +interface MySymbol { + id: string; + status: number; // 0->1->2 + isCover: boolean; + x: number; + y: number; + icon: string; } +type Scene = MySymbol[]; + +// 8*8网格 5级别level 4*4->8*8 +const makeScene: (level: number) => Scene = (level) => { + const curLevel = Math.min(5, level); + const iconPool = icons.slice(0, 2 * curLevel); + const offsetPool = [0, 25, -25, 50, -50].slice(0, 1 + curLevel); + + const scene: Scene = []; + + const range = [ + [2, 6], + [1, 6], + [1, 7], + [0, 7], + [0, 8], + ][curLevel - 1]; + + const randomSet = (icon: string) => { + const offset = + offsetPool[Math.floor(offsetPool.length * Math.random())]; + const row = + range[0] + Math.floor((range[1] - range[0]) * Math.random()); + const column = + range[0] + Math.floor((range[1] - range[0]) * Math.random()); + scene.push({ + isCover: false, + x: column * 100 + offset, + y: row * 100 + offset, + icon, + id: randomString(4), + status: 0, + }); + }; + + for (const icon of iconPool) { + for (let i = 0; i < 6; i++) { + randomSet(icon); + } + } + + return scene; +}; + +// 洗牌 +const washScene: (level: number, scene: Scene) => Scene = (level, scene) => { + const updateScene = scene.slice().sort(() => Math.random() - 0.5); + const offsetPool = [0, 25, -25, 50, -50].slice(0, 1 + level); + const range = [ + [2, 6], + [1, 6], + [1, 7], + [0, 7], + [0, 8], + ][level - 1]; + + const randomSet = (symbol: MySymbol) => { + const offset = + offsetPool[Math.floor(offsetPool.length * Math.random())]; + const row = + range[0] + Math.floor((range[1] - range[0]) * Math.random()); + const column = + range[0] + Math.floor((range[1] - range[0]) * Math.random()); + symbol.x = column * 100 + offset; + symbol.y = row * 100 + offset; + symbol.isCover = false; + }; + + for (const symbol of updateScene) { + if (symbol.status !== 0) continue; + randomSet(symbol); + } + + return updateScene; +}; + +interface SymbolProps extends MySymbol { + onClick: MouseEventHandler; +} + +const Symbol: FC = ({ x, y, icon, isCover, status, onClick }) => { + return ( +
+
+ {icon} +
+
+ ); +}; + +const App: FC = () => { + const [scene, setScene] = useState(makeScene(1)); + const [level, setLevel] = useState(1); + const [queue, setQueue] = useState([]); + const [sortedQueue, setSortedQueue] = useState< + Record + >({}); + const [finished, setFinished] = useState(false); + const [tipText, setTipText] = useState(''); + + // 队列区排序 + useEffect(() => { + console.log('fuck'); + const cache: Record = {}; + for (const symbol of queue) { + if (cache[symbol.icon]) { + cache[symbol.icon].push(symbol); + } else { + cache[symbol.icon] = [symbol]; + } + } + const temp = []; + for (const symbols of Object.values(cache)) { + temp.push(...symbols); + } + const updateSortedQueue: typeof sortedQueue = {}; + let x = 50; + for (const symbol of temp) { + updateSortedQueue[symbol.id] = x; + x += 100; + } + setSortedQueue(updateSortedQueue); + }, [queue]); + + const test = () => { + const level = Math.ceil(5 * Math.random()); + setLevel(level); + checkCover(makeScene(level)); + }; + + // 初始化覆盖状态 + useEffect(() => { + checkCover(scene); + }, []); + + // 向后检查覆盖 + const checkCover = (scene: Scene) => { + const updateScene = scene.slice(); + for (let i = 0; i < updateScene.length; i++) { + // 当前item对角坐标 + const cur = updateScene[i]; + cur.isCover = false; + if (cur.status !== 0) continue; + const { x: x1, y: y1 } = cur; + const x2 = x1 + 100, + y2 = y1 + 100; + + for (let j = i + 1; j < updateScene.length; j++) { + const compare = updateScene[j]; + if (compare.status !== 0) continue; + + // 两区域有交集视为选中 + // 两区域不重叠情况取反即为交集 + const { x, y } = compare; + + if (!(y + 100 <= y1 || y >= y2 || x + 100 <= x1 || x >= x2)) { + cur.isCover = true; + break; + } + } + } + setScene(updateScene); + }; + + // 弹出 + const pop = () => { + if (!queue.length) return; + const updateQueue = queue.slice(); + const symbol = updateQueue.shift(); + if (!symbol) return; + const find = scene.find((s) => s.id === symbol.id); + if (find) { + setQueue(updateQueue); + find.status = 0; + find.x = 100 * Math.floor(8 * Math.random()); + find.y = 700; + checkCover(scene); + } + }; + + // 撤销 + const undo = () => { + if (!queue.length) return; + const updateQueue = queue.slice(); + const symbol = updateQueue.pop(); + if (!symbol) return; + const find = scene.find((s) => s.id === symbol.id); + if (find) { + setQueue(updateQueue); + find.status = 0; + checkCover(scene); + } + }; + + // 洗牌 + const wash = () => { + checkCover(washScene(level, scene)); + }; + + // 重开 + const restart = () => { + setFinished(false); + setLevel(1); + setQueue([]); + checkCover(makeScene(1)); + }; + + // 点击item + const clickSymbol = (idx: number) => { + if (finished) return; + const updateScene = scene.slice(); + const symbol = updateScene[idx]; + if (symbol.isCover || symbol.status !== 0) return; + symbol.status++; + + let updateQueue = queue.slice(); + + const filterSame = updateQueue.filter((sb) => sb.icon === symbol.icon); + + if (filterSame.length === 2) { + // 三连了 + symbol.status++; + updateQueue = updateQueue.filter((sb) => sb.icon !== symbol.icon); + for (const sb of filterSame) { + const find = updateScene.find((i) => i.id === sb.id); + if (find) find.status++; + } + } else { + updateQueue.push(symbol); + } + + // 输了 + if (updateQueue.length === 7) { + setTipText('失败了'); + setFinished(true); + } + + if (!updateScene.find((s) => s.status !== 2)) { + // 胜利 + if (level === 5) { + setTipText('完成挑战'); + setFinished(true); + return; + } + // 升级 + setLevel(level + 1); + setQueue([]); + checkCover(makeScene(level + 1)); + } else { + setQueue(updateQueue); + checkCover(updateScene); + } + }; + + return ( + <> +

+ 有解的羊了个羊(DEMO) +

+

Level: {level}

+ +
+
+
+ {scene.map((item, idx) => ( + clickSymbol(idx)} + /> + ))} +
+
+
+
+
+ + + + {/**/} +
+ + {finished && ( +
+

{tipText}

+ +
+ )} + + ); +}; + export default App; diff --git a/src/GithubIcon.tsx b/src/GithubIcon.tsx new file mode 100644 index 0000000..e3e1d09 --- /dev/null +++ b/src/GithubIcon.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; + +export const GithubIcon: FC = () => { + return ( + + ); +}; diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/index.css b/src/index.css index 0c70022..8c1c0a1 100644 --- a/src/index.css +++ b/src/index.css @@ -25,10 +25,7 @@ a:hover { body { margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + padding: 0 32px; } h1 { diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..04f722c --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,9 @@ +export const randomString: (len: number) => string = (len) => { + const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let res = ''; + while (len >= 0) { + res += pool[Math.floor(pool.length * Math.random())]; + len--; + } + return res; +};