diff --git a/src/assets/audio/DardDealt.mp3 b/src/assets/audio/DardDealt.mp3 new file mode 100644 index 0000000..15869bd Binary files /dev/null and b/src/assets/audio/DardDealt.mp3 differ diff --git a/src/assets/audio/RoundLost.mp3 b/src/assets/audio/RoundLost.mp3 new file mode 100644 index 0000000..e64b5c2 Binary files /dev/null and b/src/assets/audio/RoundLost.mp3 differ diff --git a/src/assets/audio/RoundWon.mp3 b/src/assets/audio/RoundWon.mp3 new file mode 100644 index 0000000..a1706ac Binary files /dev/null and b/src/assets/audio/RoundWon.mp3 differ diff --git a/src/assets/audio/WarningAlert.mp3 b/src/assets/audio/WarningAlert.mp3 new file mode 100644 index 0000000..70a5b01 Binary files /dev/null and b/src/assets/audio/WarningAlert.mp3 differ diff --git a/src/components/Card/hooks/useCardPile.tsx b/src/components/Card/hooks/useCardPile.tsx index ff59230..21107fd 100644 --- a/src/components/Card/hooks/useCardPile.tsx +++ b/src/components/Card/hooks/useCardPile.tsx @@ -1,6 +1,7 @@ import { useRef, useState } from "react"; import { CardObject, Facing } from "../card.types"; import { BestHand, calculateBestHand } from "../../../utilities/deckUtilities"; +import useAudioPlayer from "../../Table/hooks/useAudioPlayer"; const defaultBestHand: BestHand = { cards: [], score: 0 }; /** @@ -13,6 +14,7 @@ export function useCardPile() { const bust = useRef(false); const fiveCardTrick = useRef(false); const allCardsVisible = useRef(false); + const {play} = useAudioPlayer(); /** * Add cards to the pile @@ -20,7 +22,10 @@ export function useCardPile() { * @param facing The direction the card should be facing when placed in the pile */ function addCards(cardsToAdd: CardObject[], facing: Facing | null = null) { - if (facing) cardsToAdd.forEach((c) => (c.facing = facing)); + cardsToAdd.forEach(card => { + play('cardDealt'); + if (facing) card.facing = facing; + }); setCards([...cards].concat(cardsToAdd)); } diff --git a/src/components/GameSettings/GameSettings.tsx b/src/components/GameSettings/GameSettings.tsx index b1f5386..d2c07b5 100644 --- a/src/components/GameSettings/GameSettings.tsx +++ b/src/components/GameSettings/GameSettings.tsx @@ -33,6 +33,22 @@ function GameSettings() { +
+
+ Audio Enabled + + Whether or not sound effects should play + +
+
+ gameSettings.toggleAudioEnabled()} + /> +
+
+
Hit Warnings Enabled diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 9c3e6f4..3f5e3b5 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -9,9 +9,11 @@ import { useGameSettingsStore } from "../../stores/gameSettingsStore"; import styles from "./Table.module.scss"; import SettingsButton from "../SettingsButton"; import { CardEffect } from "../Card/Card"; +import useAudioPlayer from "./hooks/useAudioPlayer"; const Table = ({ hide = false }: { hide?: boolean }) => { const game = useGame(); + const {play} = useAudioPlayer(); const { hitWarningsEnabled, stickWarningsEnabled } = useGameSettingsStore(); const [showHitWarning, setShowHitWarning] = useState(false); const [showStickWarning, setShowStickWarning] = useState(false); @@ -29,6 +31,7 @@ const Table = ({ hide = false }: { hide?: boolean }) => { game.getParticipantScore("Player") >= 18 && !overrideWarning ) { + play('warning'); setShowHitWarning(true); return; } @@ -44,6 +47,7 @@ const Table = ({ hide = false }: { hide?: boolean }) => { game.getParticipantScore("Player") <= 10 && !overrideWarning ) { + play('warning'); setShowStickWarning(true); return; } diff --git a/src/components/Table/hooks/useAudioPlayer.tsx b/src/components/Table/hooks/useAudioPlayer.tsx new file mode 100644 index 0000000..b5fd215 --- /dev/null +++ b/src/components/Table/hooks/useAudioPlayer.tsx @@ -0,0 +1,25 @@ +import cardDealt from "../../../assets/audio/DardDealt.mp3"; +import roundLost from "../../../assets/audio/RoundLost.mp3"; +import roundWon from "../../../assets/audio/RoundWon.mp3"; +import warning from "../../../assets/audio/WarningAlert.mp3"; +import { useGameSettingsStore } from "../../../stores/gameSettingsStore"; + +type SoundEffect = "cardDealt" | "warning" | "roundWon" | "roundLost"; + +const sounds: Record = { + cardDealt, + roundLost, + roundWon, + warning, +}; + +function useAudioPlayer() { + const { audioEnabled } = useGameSettingsStore(); + + function play(soundEffect: SoundEffect) { + audioEnabled && new Audio(sounds[soundEffect]).play(); + } + + return { play }; +} +export default useAudioPlayer; diff --git a/src/components/Table/hooks/useGame.tsx b/src/components/Table/hooks/useGame.tsx index fef8dd0..4c5b010 100644 --- a/src/components/Table/hooks/useGame.tsx +++ b/src/components/Table/hooks/useGame.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import { useGameStats } from "../../InfoHud"; import { determineWinner } from "../../../utilities/deckUtilities"; import { Facing, useCardPile, useDeck } from "../../Card"; +import useAudioPlayer from "./useAudioPlayer"; export type GameState = | "WaitingForStart" @@ -22,6 +23,8 @@ export type WinType = | "high-card" | "draw"; +const delayBetweenCards = 400; + export function useGame() { const [state, setState] = useState("WaitingForStart"); const [outcome, setOutcome] = useState(null); @@ -31,12 +34,13 @@ export function useGame() { const player = useCardPile(); const dealer = useCardPile(); const stats = useGameStats(); + const {play} = useAudioPlayer(); const dealPlayerCard = state === "DealPlayerCard"; const dealDealerCard = state === "DealDealerCard"; const resultGame = state === "Result"; const dealerRound = state === "DealerRound"; - + /** * Takes card of determining the winner * when the game state changes to Result @@ -85,7 +89,7 @@ export function useGame() { else if (dealerScore >= 16 && dealerScore > playerScore) { setState("Result"); } else { - timeout = setTimeout(() => dealCardsToParticipant("Dealer", 1), 500); + timeout = setTimeout(() => dealCardsToParticipant("Dealer", 1), delayBetweenCards); } return () => { @@ -105,7 +109,7 @@ export function useGame() { const timeout = setTimeout(() => { player.addCards(deck.take(1)); setState("DealDealerCard"); - }, 500); + }, delayBetweenCards); return () => { clearTimeout(timeout); @@ -130,7 +134,7 @@ export function useGame() { } else { setState("DealPlayerCard"); } - }, 500); + }, delayBetweenCards); return () => { clearTimeout(timeout); @@ -207,6 +211,11 @@ export function useGame() { setState("GameOver"); setOutcome(outcomeText); setWinner(winner ?? "none"); + if (winner) { + play(winner === 'Player' ? 'roundWon' : 'roundLost' ); + } + + winner && stats.updateWinnerStats(winner); } diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index 6431bc5..595a4e2 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1 +1,6 @@ /// + +declare module "*.mp3" { + const src: string; + export default src; +} diff --git a/src/stores/gameSettingsStore.ts b/src/stores/gameSettingsStore.ts index ba1f407..eab4841 100644 --- a/src/stores/gameSettingsStore.ts +++ b/src/stores/gameSettingsStore.ts @@ -6,10 +6,12 @@ export interface GameSettingsStoreState { hitWarningsEnabled: boolean; stickWarningsEnabled: boolean; settingsModalOpen: boolean; + audioEnabled: boolean; setWarningEnabled: (warningType: WarningType, enabled: boolean) => void; toggleWarningEnabled: (warningType: WarningType) => void; getWarningEnabled: (warningType: WarningType) => boolean; setSettingsModelOpen: (open: boolean) => void; + toggleAudioEnabled: () => void; } const nonPersistedKeys: Array = [ @@ -22,6 +24,7 @@ export const useGameSettingsStore = create()( hitWarningsEnabled: true, stickWarningsEnabled: true, settingsModalOpen: false, + audioEnabled: true, setWarningEnabled(warningType, enabled) { switch (warningType) { case "hit-on-high": @@ -59,6 +62,9 @@ export const useGameSettingsStore = create()( setSettingsModelOpen(open) { set({ settingsModalOpen: open }); }, + toggleAudioEnabled() { + set({ audioEnabled: !get().audioEnabled }); + }, }), { name: "game-settings",