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 + 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 (
+
+ );
+};
+
+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;
+};