Skip to content

Commit

Permalink
feat: 主题切换
Browse files Browse the repository at this point in the history
  • Loading branch information
StreakingMan committed Sep 18, 2022
1 parent 6906dec commit 968f9d6
Show file tree
Hide file tree
Showing 21 changed files with 173 additions and 36 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 能够解出来的 "羊了个羊" 小游戏 Demo

![qrcode.png](public/qrcode.png)
![qrcode.png](qrcode.png)

坑爹的小游戏(本来玩法挺有意思的,非得恶心人),根本无解(99.99%无解),气的我自己写了个 demo,
扫码或:<a href="https://solvable-sheep-game.streakingman.com/" target="_blank">pc 浏览器体验</a>
Expand All @@ -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
Expand Down
File renamed without changes
File renamed without changes
6 changes: 6 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@
transition: 0.3s;
}

.symbol-inner img {
width: 100%;
height: 100%;
object-fit: contain;
}

.queue-container {
border-radius: 8px;
width: 100%;
Expand Down
99 changes: 68 additions & 31 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {
ChangeEventHandler,
FC,
MouseEventHandler,
useEffect,
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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 =
Expand Down Expand Up @@ -127,14 +131,20 @@ const Symbol: FC<SymbolProps> = ({ x, y, icon, isCover, status, onClick }) => {
className="symbol-inner"
style={{ backgroundColor: isCover ? '#999' : 'white' }}
>
<i>{icon}</i>
{typeof icon.content === 'string' ? (
<i>{icon.content}</i>
) : (
icon.content
)}
</div>
</div>
);
};

const App: FC = () => {
const [scene, setScene] = useState<Scene>(makeScene(1));
const [curTheme, setCurTheme] =
useState<Theme<DefaultSoundNames>>(defaultTheme);
const [scene, setScene] = useState<Scene>(makeScene(1, curTheme.icons));
const [level, setLevel] = useState<number>(1);
const [queue, setQueue] = useState<MySymbol[]>([]);
const [sortedQueue, setSortedQueue] = useState<
Expand All @@ -143,13 +153,14 @@ const App: FC = () => {
const [finished, setFinished] = useState<boolean>(false);
const [tipText, setTipText] = useState<string>('');
const [animating, setAnimating] = useState<boolean>(false);

// 音效
const soundRefMap = useRef<Record<string, HTMLAudioElement>>({});

// 第一次点击时播放bgm
const bgmRef = useRef<HTMLAudioElement>(null);
const [bgmOn, setBgmOn] = useState<boolean>(false);
const [once, setOnce] = useState<boolean>(false);
const tapSoundRef = useRef<HTMLAudioElement>(null);
const tripleSoundRef = useRef<HTMLAudioElement>(null);
const levelUpSoundRef = useRef<HTMLAudioElement>(null);

useEffect(() => {
if (bgmOn) {
bgmRef.current?.play();
Expand All @@ -158,14 +169,19 @@ const App: FC = () => {
}
}, [bgmOn]);

// 主题切换
useEffect(() => {
restart();
}, [curTheme]);

// 队列区排序
useEffect(() => {
const cache: Record<string, MySymbol[]> = {};
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 = [];
Expand All @@ -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);
Expand Down Expand Up @@ -264,15 +274,15 @@ const App: FC = () => {
setFinished(false);
setLevel(level + 1);
setQueue([]);
checkCover(makeScene(level + 1));
checkCover(makeScene(level + 1, curTheme.icons));
};

// 重开
const restart = () => {
setFinished(false);
setLevel(1);
setQueue([]);
checkCover(makeScene(1));
checkCover(makeScene(1, curTheme.icons));
};

// 点击item
Expand All @@ -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();
Expand All @@ -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();
}
}
}
Expand All @@ -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);
Expand All @@ -351,7 +365,21 @@ const App: FC = () => {
<h6>
<GithubIcon />
</h6>
<h3>Level: {level} </h3>
<h3 className="flex-container flex-center">
主题:
<select
onChange={(e) =>
setCurTheme(themes[Number(e.target.value)])
}
>
{themes.map((t, idx) => (
<option key={t.name} value={idx}>
{t.name}
</option>
))}
</select>
Level: {level}
</h3>

<div className="app">
<div className="scene-container">
Expand Down Expand Up @@ -404,13 +432,22 @@ const App: FC = () => {
</div>
)}

{/*bgm*/}
<button className="bgm-button" onClick={() => setBgmOn(!bgmOn)}>
{bgmOn ? '🔊' : '🔈'}
<audio ref={bgmRef} loop src="/sound-disco.mp3" />
</button>

<audio ref={tapSoundRef} src="/sound-button-click.mp3" />
<audio ref={tripleSoundRef} src="/sound-triple.mp3" />
{/*音效*/}
{curTheme.sounds.map((sound) => (
<audio
key={sound.name}
ref={(ref) => {
if (ref) soundRefMap.current[sound.name] = ref;
}}
src={sound.src}
/>
))}
</>
);
};
Expand Down
6 changes: 6 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ h1 {
line-height: 1.1;
}

select {
border: 2px solid gray;
border-radius: 4px;
padding: 4px 8px;
}

button {
border-radius: 8px;
border: 1px solid transparent;
Expand Down
40 changes: 40 additions & 0 deletions src/themes/default/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Theme } from '../interface';

const icons = <const>[
`🎨`,
`🌈`,
`⚙️`,
`💻`,
`📚`,
`🐯`,
`🐤`,
`🐼`,
`🐏`,
`🍀`,
];

export type DefaultSoundNames = 'button-click' | 'triple';

import soundButtonClickUrl from './sounds/sound-button-click.mp3';
import soundTripleUrl from './sounds/sound-triple.mp3';
export const defaultSounds: Theme<DefaultSoundNames>['sounds'] = [
{
name: 'button-click',
src: soundButtonClickUrl,
},
{
name: 'triple',
src: soundTripleUrl,
},
];

export const defaultTheme: Theme<DefaultSoundNames> = {
name: '默认',
icons: icons.map((icon) => ({
name: icon,
content: icon,
clickSound: 'button-click',
tripleSound: 'triple',
})),
sounds: defaultSounds,
};
File renamed without changes.
File renamed without changes.
Binary file added src/themes/fisherman/images/塘鳢鱼.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/themes/fisherman/images/河豚.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/themes/fisherman/images/锦鲤.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/themes/fisherman/images/鲈鱼.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/themes/fisherman/images/鲑鱼.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/themes/fisherman/images/鲤鱼.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/themes/fisherman/images/鲨鱼.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/themes/fisherman/images/鲫鱼.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/themes/fisherman/images/鳖.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/themes/fisherman/images/黑鱼.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions src/themes/fisherman/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 钓鱼佬主题
import React from 'react';
import { Theme } from '../interface';
import { DefaultSoundNames, defaultSounds } from '../default';

const imagesUrls = import.meta.glob('./images/*.png', {
import: 'default',
eager: true,
});

const fishes = Object.entries(imagesUrls).map(([key, value]) => ({
name: key.slice(9, -4),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
content: <img src={value} alt="" />,
}));

export const fishermanTheme: Theme<DefaultSoundNames> = {
name: '钓鱼佬',
icons: fishes.map(({ name, content }) => ({
name,
content,
clickSound: 'button-click',
tripleSound: 'triple',
})),
sounds: defaultSounds,
};
19 changes: 19 additions & 0 deletions src/themes/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ReactNode } from 'react';

export interface Icon<T = string> {
name: string;
content: ReactNode;
clickSound: T;
tripleSound: T;
}

interface Sound<T = string> {
name: T;
src: string;
}

export interface Theme<SoundNames> {
name: string;
icons: Icon<SoundNames>[];
sounds: Sound<SoundNames>[];
}

1 comment on commit 968f9d6

@vercel
Copy link

@vercel vercel bot commented on 968f9d6 Sep 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.