diff --git a/apps/client/public/sounds/standard/Capture.mp3 b/apps/client/public/sounds/standard/Capture.mp3 new file mode 100644 index 0000000..ab51d76 Binary files /dev/null and b/apps/client/public/sounds/standard/Capture.mp3 differ diff --git a/apps/client/public/sounds/standard/Castle.mp3 b/apps/client/public/sounds/standard/Castle.mp3 new file mode 100644 index 0000000..0fe64ec Binary files /dev/null and b/apps/client/public/sounds/standard/Castle.mp3 differ diff --git a/apps/client/public/sounds/standard/Check.mp3 b/apps/client/public/sounds/standard/Check.mp3 new file mode 100644 index 0000000..82cce21 Binary files /dev/null and b/apps/client/public/sounds/standard/Check.mp3 differ diff --git a/apps/client/public/sounds/standard/Game-End.mp3 b/apps/client/public/sounds/standard/Game-End.mp3 new file mode 100644 index 0000000..d5efe4e Binary files /dev/null and b/apps/client/public/sounds/standard/Game-End.mp3 differ diff --git a/apps/client/public/sounds/standard/GenericNotify.mp3 b/apps/client/public/sounds/standard/GenericNotify.mp3 new file mode 100644 index 0000000..61bb1b6 Binary files /dev/null and b/apps/client/public/sounds/standard/GenericNotify.mp3 differ diff --git a/apps/client/public/sounds/standard/Move.mp3 b/apps/client/public/sounds/standard/Move.mp3 new file mode 100644 index 0000000..7ed0cf6 Binary files /dev/null and b/apps/client/public/sounds/standard/Move.mp3 differ diff --git a/apps/client/public/sounds/standard/Promote.mp3 b/apps/client/public/sounds/standard/Promote.mp3 new file mode 100644 index 0000000..25242a5 Binary files /dev/null and b/apps/client/public/sounds/standard/Promote.mp3 differ diff --git a/apps/client/src/hooks/game/useChessGame.ts b/apps/client/src/hooks/game/useChessGame.ts index ad5aa5b..bfb9126 100644 --- a/apps/client/src/hooks/game/useChessGame.ts +++ b/apps/client/src/hooks/game/useChessGame.ts @@ -10,9 +10,10 @@ import type { } from "@chesslab/shared/types"; import { Chess } from "chess.js"; import { useCallback, useEffect, useState } from "react"; -import { useSocket } from "../useSocket"; +import { useSocket } from "@hooks/useSocket"; import type { ChessboardOptions, PieceDropHandlerArgs } from "react-chessboard"; -import useTimer from "./useTimer"; +import useTimer from "@hooks/game/useTimer"; +import { playMoveSound } from "@services/sfx/index"; export function useChessGame(gameId?: string) { const { socket } = useSocket(); @@ -34,6 +35,7 @@ export function useChessGame(gameId?: string) { const handleMove = useCallback( ({ fen, turn, timeInfo }: MoveMadeEvent) => { + playMoveSound(chessGame); chessGame.load(fen); setChessPosition(fen); setTurn(turn); diff --git a/apps/client/src/hooks/game/useComputerGame.ts b/apps/client/src/hooks/game/useComputerGame.ts index 453f4b6..4a55c4f 100644 --- a/apps/client/src/hooks/game/useComputerGame.ts +++ b/apps/client/src/hooks/game/useComputerGame.ts @@ -4,13 +4,15 @@ import { Chess } from "chess.js"; import { useState } from "react"; import { getGameOverInfo } from "@chesslab/shared/utils"; import { useCompTimer } from "./useCompTimer"; +import { playMoveSound } from "@services/sfx/index"; export function useComputerGame() { const [chessGame] = useState(() => new Chess()); const [chessPosition, setChessPosition] = useState(() => new Chess().fen()); const [turn, setTurn] = useState<"w" | "b">("w"); const [gameHistory, setGameHistory] = useState([]); - const [side] = useState<"w" | "b">(() => (Math.random() < 0.5 ? "w" : "b")); + // const [side] = useState<"w" | "b">(() => (Math.random() < 0.5 ? "w" : "b")); + const [side] = useState<"w" | "b">(() => "w"); let gameOverInfo = getGameOverInfo(chessGame); @@ -37,6 +39,7 @@ export function useComputerGame() { setChessPosition(chessGame.fen()); setTurn(chessGame.turn()); setGameHistory(chessGame.history()); + playMoveSound(chessGame); }, }); @@ -53,6 +56,8 @@ export function useComputerGame() { if (!move) return false; setChessPosition(chessGame.fen()); + + playMoveSound(chessGame); setTurn(chessGame.turn()); setGameHistory(chessGame.history()); diff --git a/apps/client/src/services/sfx/index.ts b/apps/client/src/services/sfx/index.ts new file mode 100644 index 0000000..d26aa59 --- /dev/null +++ b/apps/client/src/services/sfx/index.ts @@ -0,0 +1,44 @@ +import type { Chess } from "chess.js"; + +const sounds = { + move: new Audio("/sounds/standard/Move.mp3"), + check: new Audio("/sounds/standard/Check.mp3"), + capture: new Audio("/sounds/standard/Capture.mp3"), + checkmate: new Audio("/sounds/standard/Game-End.mp3"), + castle: new Audio("/sounds/standard/Castle.mp3"), + promotion: new Audio("/sounds/standard/Promote.mp3"), + defeat: new Audio("/sounds/standard/GenericNotify.mp3"), + draw: new Audio("/sounds/standard/GenericNotify.mp3"), +}; + +function playSound(sound: keyof typeof sounds) { + const audio = sounds[sound]; + + audio.currentTime = 0; + audio.play(); +} + +export function playMoveSound(chess: Chess) { + if (chess.isGameOver()) { + if (chess.isCheckmate()) playSound("checkmate"); + else if (chess.isDraw()) playSound("draw"); + else playSound("defeat"); + return; + } + + const moves = chess.history({ verbose: true }); + if (moves.length > 0) { + const lastMove = moves[moves.length - 1]; + if (lastMove?.san.endsWith("+")) { + playSound("check"); + } else if (lastMove?.isCapture()) { + playSound("capture"); + } else if (lastMove?.isKingsideCastle() || lastMove?.isQueensideCastle()) { + playSound("castle"); + } else if (lastMove?.isPromotion()) { + playSound("promotion"); + } else { + playSound("move"); + } + } +} diff --git a/apps/client/tsconfig.json b/apps/client/tsconfig.json index 7f2addf..cc5b01b 100644 --- a/apps/client/tsconfig.json +++ b/apps/client/tsconfig.json @@ -21,7 +21,8 @@ "@pages/*": ["./src/pages/*"], "@stockfish/*": ["./src/stockfish/*"], "@app-types/*": ["./src/types/*"], - "@utils/*": ["./src/utils/*"] + "@utils/*": ["./src/utils/*"], + "@services/*": ["./src/services/*"] } }, "include": ["vite.config.ts", "src"] diff --git a/apps/client/vite.config.ts b/apps/client/vite.config.ts index ade004a..a7169db 100644 --- a/apps/client/vite.config.ts +++ b/apps/client/vite.config.ts @@ -20,6 +20,7 @@ export default defineConfig({ "@stockfish": path.resolve(__dirname, "./src/stockfish"), "@app-types": path.resolve(__dirname, "./src/types"), "@utils": path.resolve(__dirname, "./src/utils"), + "@services": path.resolve(__dirname, "./src/services"), }, }, server: {