diff --git a/README.md b/README.md index 86fb710..31927de 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 能够解出来的 "羊了个羊" 小游戏 Demo -![qrcode.png](public/qrcode.png) +![qrcode.png](qrcode.png) 坑爹的小游戏(本来玩法挺有意思的,非得恶心人),根本无解(99.99%无解),气的我自己写了个 demo, 扫码或:pc 浏览器体验 @@ -14,22 +14,24 @@ 开心就好 😄 -![preview.png](public/preview.png) +![preview.png](preview.png) ## Contribution vite+react 实现,欢迎 star、issue、pr、fork(尽量标注原仓库地址) +切换主题参考 `src/themes` 下的代码,欢迎整活 + ## Todo List - [x] 基础操作 - [x] 关卡生成 - [ ] UI/UX 优化 -- [ ] 多主题 +- [x] 多主题 - [ ] 计时 - [ ] 性能优化 -- [ ] BGM/音效 -- [ ] 点击时的缓冲队列,优化交互动画效果 +- [x] BGM/音效 +- [ ] ~~点击时的缓冲队列,优化交互动画效果~~ - [ ] 该游戏似乎涉嫌抄袭,考证后补充来源说明 ## License diff --git a/public/preview.png b/preview.png similarity index 100% rename from public/preview.png rename to preview.png diff --git a/public/qrcode.png b/qrcode.png similarity index 100% rename from public/qrcode.png rename to qrcode.png diff --git a/src/App.css b/src/App.css index 6e80bfb..a41e174 100644 --- a/src/App.css +++ b/src/App.css @@ -50,6 +50,12 @@ transition: 0.3s; } +.symbol-inner img { + width: 100%; + height: 100%; + object-fit: contain; +} + .queue-container { border-radius: 8px; width: 100%; diff --git a/src/App.tsx b/src/App.tsx index b4fc950..70ba885 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import React, { + ChangeEventHandler, FC, MouseEventHandler, useEffect, @@ -9,8 +10,11 @@ import React, { import './App.css'; import { GithubIcon } from './GithubIcon'; import { randomString, waitTimeout } from './utils'; +import { DefaultSoundNames, defaultTheme } from './themes/default'; +import { Icon, Theme } from './themes/interface'; +import { fishermanTheme } from './themes/fisherman'; -const icons = [`🎨`, `🌈`, `⚙️`, `💻`, `📚`, `🐯`, `🐤`, `🐼`, `🐏`, `🍀`]; +const themes = [defaultTheme, fishermanTheme]; // 最大关卡 const maxLevel = 50; @@ -21,13 +25,13 @@ interface MySymbol { isCover: boolean; x: number; y: number; - icon: string; + icon: Icon; } type Scene = MySymbol[]; // 8*8网格 4*4->8*8 -const makeScene: (level: number) => Scene = (level) => { +const makeScene: (level: number, icons: Icon[]) => Scene = (level, icons) => { const curLevel = Math.min(maxLevel, level); const iconPool = icons.slice(0, 2 * curLevel); const offsetPool = [0, 25, -25, 50, -50].slice(0, 1 + curLevel); @@ -42,7 +46,7 @@ const makeScene: (level: number) => Scene = (level) => { [0, 8], ][Math.min(4, curLevel - 1)]; - const randomSet = (icon: string) => { + const randomSet = (icon: Icon) => { const offset = offsetPool[Math.floor(offsetPool.length * Math.random())]; const row = @@ -127,14 +131,20 @@ const Symbol: FC = ({ x, y, icon, isCover, status, onClick }) => { className="symbol-inner" style={{ backgroundColor: isCover ? '#999' : 'white' }} > - {icon} + {typeof icon.content === 'string' ? ( + {icon.content} + ) : ( + icon.content + )} ); }; const App: FC = () => { - const [scene, setScene] = useState(makeScene(1)); + const [curTheme, setCurTheme] = + useState>(defaultTheme); + const [scene, setScene] = useState(makeScene(1, curTheme.icons)); const [level, setLevel] = useState(1); const [queue, setQueue] = useState([]); const [sortedQueue, setSortedQueue] = useState< @@ -143,13 +153,14 @@ const App: FC = () => { const [finished, setFinished] = useState(false); const [tipText, setTipText] = useState(''); const [animating, setAnimating] = useState(false); + + // 音效 + const soundRefMap = useRef>({}); + + // 第一次点击时播放bgm const bgmRef = useRef(null); const [bgmOn, setBgmOn] = useState(false); const [once, setOnce] = useState(false); - const tapSoundRef = useRef(null); - const tripleSoundRef = useRef(null); - const levelUpSoundRef = useRef(null); - useEffect(() => { if (bgmOn) { bgmRef.current?.play(); @@ -158,14 +169,19 @@ const App: FC = () => { } }, [bgmOn]); + // 主题切换 + useEffect(() => { + restart(); + }, [curTheme]); + // 队列区排序 useEffect(() => { const cache: Record = {}; for (const symbol of queue) { - if (cache[symbol.icon]) { - cache[symbol.icon].push(symbol); + if (cache[symbol.icon.name]) { + cache[symbol.icon.name].push(symbol); } else { - cache[symbol.icon] = [symbol]; + cache[symbol.icon.name] = [symbol]; } } const temp = []; @@ -181,12 +197,6 @@ const App: FC = () => { setSortedQueue(updateSortedQueue); }, [queue]); - const test = () => { - const level = Math.ceil(maxLevel * Math.random()); - setLevel(level); - checkCover(makeScene(level)); - }; - // 初始化覆盖状态 useEffect(() => { checkCover(scene); @@ -264,7 +274,7 @@ const App: FC = () => { setFinished(false); setLevel(level + 1); setQueue([]); - checkCover(makeScene(level + 1)); + checkCover(makeScene(level + 1, curTheme.icons)); }; // 重开 @@ -272,7 +282,7 @@ const App: FC = () => { setFinished(false); setLevel(1); setQueue([]); - checkCover(makeScene(1)); + checkCover(makeScene(1, curTheme.icons)); }; // 点击item @@ -289,9 +299,10 @@ const App: FC = () => { if (symbol.isCover || symbol.status !== 0) return; symbol.status = 1; - if (tapSoundRef.current) { - tapSoundRef.current.currentTime = 0; - tapSoundRef.current.play(); + // 点击音效 + if (soundRefMap.current) { + soundRefMap.current[symbol.icon.clickSound].currentTime = 0; + soundRefMap.current[symbol.icon.clickSound].play(); } let updateQueue = queue.slice(); @@ -312,9 +323,12 @@ const App: FC = () => { const find = updateScene.find((i) => i.id === sb.id); if (find) { find.status = 2; - if (tripleSoundRef.current) { - tripleSoundRef.current.currentTime = 0.55; - tripleSoundRef.current.play(); + // 三连音效 + if (soundRefMap.current) { + soundRefMap.current[ + symbol.icon.tripleSound + ].currentTime = 0; + soundRefMap.current[symbol.icon.tripleSound].play(); } } } @@ -336,7 +350,7 @@ const App: FC = () => { // 升级 setLevel(level + 1); setQueue([]); - checkCover(makeScene(level + 1)); + checkCover(makeScene(level + 1, curTheme.icons)); } else { setQueue(updateQueue); checkCover(updateScene); @@ -351,7 +365,21 @@ const App: FC = () => {
-

Level: {level}

+

+ 主题: + + Level: {level} +

@@ -404,13 +432,22 @@ const App: FC = () => {
)} + {/*bgm*/} -