Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"cookie": "^1.0.2",
"embla-carousel": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"howler": "^2.2.4",
"react": "^19.1.0",
"react-countup": "^6.5.3",
"react-dom": "^19.1.0",
Expand All @@ -38,6 +39,7 @@
"devDependencies": {
"@eslint/js": "^9.30.1",
"@hex-hunt-app/types": "*",
"@types/howler": "^2.2.12",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
Expand Down
Binary file added apps/web/public/audio/next-move.mp3
Binary file not shown.
Binary file added apps/web/public/audio/sci-fi-gun-shot.mp3
Binary file not shown.
38 changes: 22 additions & 16 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import GameProvider from './providers/GameProvider';
import WindowSizeProvider from './providers/WindowSizeProvider';
import PrivacyPolicyPage from './pages/PrivacyPolicyPage';
import GamePage from './pages/GamePage';
import AudioProvider from './providers/AudioProvider';

const queryClient = new QueryClient();

Expand All @@ -23,22 +24,27 @@ function App() {
<QueryClientProvider client={queryClient}>
<AuthProvider>
<WindowSizeProvider>
<GameProvider>
<Switch>
<Route path={'/login'} component={() => <LoginPage />} />
<Route
path={'/select-game'}
component={() => <div>Select Game</div>}
/>
<Route path={'/'} component={() => <FindGamePage />} />
<Route path={'/game'} component={() => <GamePage />} />
<Route
path={'/privacy-policy'}
component={() => <PrivacyPolicyPage />}
/>
<Route path={'*'} component={() => <div>Error page</div>} />
</Switch>{' '}
</GameProvider>
<AudioProvider>
<GameProvider>
<Switch>
<Route path={'/login'} component={() => <LoginPage />} />
<Route
path={'/select-game'}
component={() => <div>Select Game</div>}
/>
<Route path={'/'} component={() => <FindGamePage />} />
<Route path={'/game'} component={() => <GamePage />} />
<Route
path={'/privacy-policy'}
component={() => <PrivacyPolicyPage />}
/>
<Route
path={'*'}
component={() => <div>Error page</div>}
/>
</Switch>{' '}
</GameProvider>
</AudioProvider>
</WindowSizeProvider>
</AuthProvider>
</QueryClientProvider>
Expand Down
16 changes: 16 additions & 0 deletions apps/web/src/hooks/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { EventType } from '../components/AnimatedPopup/AnimatedPopup';
import type { GameData } from '../utils/GameData';
import { PlayerType } from '../utils/Player';
import type { Hex } from '../utils/Hex';
import { SoundSource, useAudio } from '../providers/AudioProvider';

export type ImgRef = {
astronaut: HTMLImageElement | null;
Expand Down Expand Up @@ -72,6 +73,7 @@ export const useInitializeSockets = (
) => {
const socketRef = useRef<Socket | null>(null);
const [, navigate] = useLocation();
const { setSound, setMove } = useAudio();

useEffect(() => {
socketRef.current = io(import.meta.env.VITE_API_URL, {
Expand Down Expand Up @@ -117,6 +119,18 @@ export const useInitializeSockets = (
setGameState(data.game);
});
socketRef.current.on('gameState', (data: GameData) => {
const someoneDied = data.players.some(
(p) => p.diedAtMove === data.moves - 1,
);

if (someoneDied) {
setSound(SoundSource.SCI_FI_GUN_SHOT);
} else {
setSound(SoundSource.NEW_TURN);
}

setMove(data.moves);

toast('Next move', {
style: {
background: '#1b1f2d',
Expand Down Expand Up @@ -186,6 +200,8 @@ export const useInitializeSockets = (
setShowPopup,
setPopupEvents,
setShouldResetEventDate,
setSound,
setMove,
]);

return socketRef;
Expand Down
69 changes: 69 additions & 0 deletions apps/web/src/providers/AudioProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createContext, useContext, useEffect, useRef, useState } from 'react';
import { Howl } from 'howler';

// eslint-disable-next-line react-refresh/only-export-components
export enum SoundSource {
NEW_TURN = '/audio/next-move.mp3',
SCI_FI_GUN_SHOT = '/audio/sci-fi-gun-shot.mp3',
}

type AudioContextType = {
setSound: (src: string | null) => void;
setMove: (move: number) => void;
};

const initialContextValue: AudioContextType = {
setSound: () => {},
setMove: () => {},
};

const AudioContext = createContext<AudioContextType>(initialContextValue);

type Props = {
children: React.ReactNode;
};

const AudioProvider: React.FC<Props> = ({ children }) => {
const soundRef = useRef<Howl | null>(null);
const [sound, setSound] = useState<string | null>(null);
const [move, setMove] = useState<number>(0);

const playNewSound = (src: string) => {
const newSound = new Howl({
src: [src],
autoplay: true,
loop: false,
volume: 0.5,
onload: () => console.log('sound loaded'),
onplayerror: (id, error) => console.error('sound play error', id, error),
onloaderror: (id, error) => console.error('sound load error', id, error),
});

newSound.play();
soundRef.current = newSound;
};

useEffect(() => {
if (soundRef.current) {
const oldSound = soundRef.current;
oldSound.stop();
oldSound.unload();
}

if (sound) playNewSound(sound);
}, [sound, move]);

const value = {
setSound,
setMove,
};

return (
<AudioContext.Provider value={value}>{children}</AudioContext.Provider>
);
};

export default AudioProvider;

// eslint-disable-next-line react-refresh/only-export-components
export const useAudio = () => useContext(AudioContext);
16 changes: 16 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5052,6 +5052,13 @@ __metadata:
languageName: node
linkType: hard

"@types/howler@npm:^2.2.12":
version: 2.2.12
resolution: "@types/howler@npm:2.2.12"
checksum: 0f8b88abd9b0607db50c4fbc91cc6646a9eee4a337f3e8b9c39696e78eff1eb0b3f5913bf32cccc7a96d2be2af8544376ce3afa5de4b1ba9873d0287abe97a5b
languageName: node
linkType: hard

"@types/http-cache-semantics@npm:^4.0.2":
version: 4.0.4
resolution: "@types/http-cache-semantics@npm:4.0.4"
Expand Down Expand Up @@ -10046,6 +10053,13 @@ __metadata:
languageName: node
linkType: hard

"howler@npm:^2.2.4":
version: 2.2.4
resolution: "howler@npm:2.2.4"
checksum: 14ad7ed8825ac4439a63429d25f3ba6f44daca1d6df4ad6b04f33e342269b80db5608d9f1e6e980e2d47027c7ceba7811068eba7f56e8e75f2601086e07f0b73
languageName: node
linkType: hard

"html-escaper@npm:^2.0.0":
version: 2.0.2
resolution: "html-escaper@npm:2.0.2"
Expand Down Expand Up @@ -15745,6 +15759,7 @@ __metadata:
"@solana/wallet-adapter-wallets": ^0.19.37
"@solana/web3.js": ^1.98.4
"@tanstack/react-query": ^5.84.1
"@types/howler": ^2.2.12
"@types/react": ^19.1.8
"@types/react-dom": ^19.1.6
"@types/socket.io": ^3.0.2
Expand All @@ -15759,6 +15774,7 @@ __metadata:
eslint-plugin-react-hooks: ^5.2.0
eslint-plugin-react-refresh: ^0.4.20
globals: ^16.3.0
howler: ^2.2.4
react: ^19.1.0
react-countup: ^6.5.3
react-dom: ^19.1.0
Expand Down