Skip to content

Commit 998c67e

Browse files
committed
Adds Audio to certain minigames & Pin Length setting for PinCracker
1 parent 2da1560 commit 998c67e

File tree

8 files changed

+110
-77
lines changed

8 files changed

+110
-77
lines changed

app/puzzles/pincracker/Pincracker.tsx

+75-70
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,43 @@
11
"use client";
22

3-
import { Digit, Digits, DigitState } from "@/app/puzzles/pincracker/utils";
3+
import { checkBeepPlayer, successPlayer } from "@/public/audio/AudioManager";
4+
import { Digit, Digits } from "@/app/puzzles/pincracker/utils";
45
import NPHackContainer from "@/app/components/NPHackContainer";
56
import { NPSettingsRange } from "@/app/components/NPSettings";
6-
import React, { FC, useEffect, useState } from "react";
7-
import { getStatusMessage } from "../thermite/utils";
7+
import React, { FC, useEffect, useState, useRef } from "react";
88
import usePersistantState from "@/app/utils/usePersistentState";
99
import { useKeyDown } from "@/app/utils/useKeyDown";
1010
import useGame from "@/app/utils/useGame";
11-
import classNames from "classnames";
12-
import { clear } from "console";
13-
import { generate } from "random-words";
1411

1512
const defaultDuration = 20;
1613

14+
const getStatusMessage = (status: number | undefined) => {
15+
switch (status) {
16+
case 0:
17+
return "";
18+
case 1:
19+
return "";
20+
case 2:
21+
return "Failed!";
22+
case 3:
23+
return "Success!";
24+
case 4:
25+
return "Reset!";
26+
default:
27+
return `Error: Unknown game status ${status}`;
28+
}
29+
}
30+
1731
const Pincracker: FC = () => {
1832
const [timer, setTimer] = usePersistantState("chopping-timer", defaultDuration);
1933
const [settingsDuration, setSettingsDuration] = useState(defaultDuration);
2034
const [activeIndex, setActiveIndex] = usePersistantState("pincracker-active-index", 0);
2135
const [allowKeyDown, setAllowKeyDown] = useState(true);
36+
const [pinLength, setPinLength] = useState(4);
2237
const [pin, setPin] = useState<Digit[]>();
2338

2439
const handleCrack = () => {
25-
if (activeIndex < 4) {
40+
if (activeIndex < pinLength) {
2641
console.log('Incomplete pin');
2742
} else {
2843
const wrappers = document.querySelectorAll('.wrapper');
@@ -31,8 +46,12 @@ const Pincracker: FC = () => {
3146
const guess = Array.from(digits).map(d => (d.innerHTML as Digit));
3247
setAllowKeyDown(false);
3348

34-
for (let i = 0; i < 4; i++) {
49+
50+
for (let i = 0; i < pinLength; i++) {
3551
setTimeout(() => {
52+
// Play the check beep audio
53+
checkBeepPlayer.play();
54+
3655
// Remove the background color of the previous wrapper if it exists
3756
if (i > 0) {
3857
wrappers[i - 1].classList.remove('bg-gradient-radial', 'from-spring-green-300', 'to-turquoise-900/50');
@@ -66,7 +85,6 @@ const Pincracker: FC = () => {
6685

6786
setTimeout(() => {
6887
if (pin && guess.join('') === pin.join('')) {
69-
console.log('Success!');
7088
setGameStatus(3);
7189
}
7290
setActiveIndex(0);
@@ -77,15 +95,15 @@ const Pincracker: FC = () => {
7795

7896
const clearBoard = (delay: number) => {
7997
const digits = document.querySelectorAll('.digit');
80-
for (let i = 3; i > -1; i--) {
98+
for (let i = pinLength-1; i > -1; i--) {
8199
setTimeout(() => {
82100
digits[i].innerHTML = '';
83-
}, (4-i) * delay);
101+
}, (pinLength-i) * delay);
84102
}
85103

86104
setTimeout(() => {
87105
setAllowKeyDown(true);
88-
}, delay * 4);
106+
}, delay * pinLength);
89107
}
90108

91109
const clearMarkings = () => {
@@ -104,7 +122,7 @@ const Pincracker: FC = () => {
104122
[Digits[i], Digits[j]] = [Digits[j], Digits[i]];
105123
}
106124

107-
const newPin = Digits.slice(0, 4);
125+
const newPin = Digits.slice(0, pinLength);
108126
setPin(newPin);
109127
}
110128

@@ -121,9 +139,7 @@ const Pincracker: FC = () => {
121139
switch (newStatus) {
122140
case 1:
123141
setAllowKeyDown(false);
124-
console.log('Reset game');
125142
resetBoard();
126-
127143
break;
128144
}
129145
}
@@ -150,7 +166,7 @@ const Pincracker: FC = () => {
150166
}
151167

152168
else {
153-
if (activeIndex < 4) {
169+
if (activeIndex < pinLength) {
154170
const digits = document.querySelectorAll('.digit');
155171
digits[activeIndex].innerHTML = key.toString();
156172
setActiveIndex(activeIndex + 1);
@@ -166,10 +182,10 @@ const Pincracker: FC = () => {
166182
}, ['1','2','3','4','5','6','7','8','9','0', 'Backspace', 'Enter']);
167183

168184
useEffect(() => {
169-
if (gameStatus !== 4) {
170-
resetGame();
185+
if (gameStatus === 3) {
186+
successPlayer.play();
171187
}
172-
}, [timer])
188+
}, [gameStatus])
173189

174190
const settings = {
175191
handleSave: () => {
@@ -184,6 +200,13 @@ const Pincracker: FC = () => {
184200

185201
children: (
186202
<div className="flex flex-col items-center">
203+
<NPSettingsRange
204+
title={"Pin Length"}
205+
min={2}
206+
max={6}
207+
value={pinLength}
208+
setValue={setPinLength}
209+
/>
187210
<NPSettingsRange
188211
title={"Duration (seconds)"}
189212
min={5}
@@ -197,58 +220,40 @@ const Pincracker: FC = () => {
197220

198221
return (
199222
<NPHackContainer
200-
title="PinCracker"
201-
description="Decode digits of the pin code"
202-
buttons={[
203-
[
204-
{
205-
label: "Crack",
206-
color: "green",
207-
callback: handleCrack,
208-
disabled: gameStatus !== 1,
209-
}
210-
]
211-
]}
212-
countdownDuration={timer*1000}
213-
resetCallback={resetGame}
214-
resetDelay={3000}
215-
status={gameStatus}
216-
setStatus={setGameStatus}
217-
statusMessage={getStatusMessage(gameStatus)}
218-
settings={settings}
223+
title="PinCracker"
224+
description="Decode digits of the pin code"
225+
buttons={[
226+
[
227+
{
228+
label: "Crack",
229+
color: "green",
230+
callback: handleCrack,
231+
disabled: gameStatus !== 1,
232+
}
233+
]
234+
]}
235+
countdownDuration={timer * 1000}
236+
resetCallback={resetGame}
237+
resetDelay={3000}
238+
status={gameStatus}
239+
setStatus={setGameStatus}
240+
statusMessage={getStatusMessage(gameStatus)}
241+
settings={settings}
219242
>
220-
221-
<div className="
222-
h-32 w-[600px] max-w-full
223-
rounded-lg
224-
bg-[rgb(22_40_52)]
225-
flex items-center justify-between
226-
text-white text-5xl
227-
228-
">
229-
230-
<div className="flex flex-col items-center justify-center w-3/12 h-full gap-3 rounded-md wrapper">
231-
<div className='h-[50px] digit'></div>
232-
<div className="px-5 h-1 bg-slate-400 marker"/>
233-
</div>
234-
235-
<div className="flex flex-col items-center justify-center w-3/12 h-full gap-3 rounded-md wrapper">
236-
<div className='h-[50px] digit'></div>
237-
<div className="px-5 h-1 bg-slate-400 marker"/>
238-
</div>
239-
240-
<div className="flex flex-col items-center justify-center w-3/12 h-full gap-3 rounded-md wrapper">
241-
<div className='h-[50px] digit'></div>
242-
<div className="px-5 h-1 bg-slate-400 marker"/>
243-
</div>
244-
245-
<div className="flex flex-col items-center justify-center w-3/12 h-full gap-3 rounded-md wrapper">
246-
<div className='h-[50px] digit'></div>
247-
<div className="px-5 h-1 bg-slate-400 marker"/>
248-
</div>
249-
250-
</div>
251-
243+
<div className="
244+
h-32 w-[600px] max-w-full
245+
rounded-lg
246+
bg-[rgb(22_40_52)]
247+
flex items-center justify-between
248+
text-white text-5xl
249+
">
250+
{[...Array(pinLength)].map((_, index) => (
251+
<div key={index} className="flex flex-col items-center justify-center w-3/12 h-full gap-3 rounded-md wrapper">
252+
<div className='h-[50px] digit'></div>
253+
<div className="px-5 h-1 bg-slate-400 marker"/>
254+
</div>
255+
))}
256+
</div>
252257
</NPHackContainer>
253258
);
254259
}

app/puzzles/pincracker/utils.ts

-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
11
export type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
2-
export type DigitState = 'correct' | 'position' | 'wrong';
3-
42
export const Digits: Digit[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];

app/utils/useCountdown.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import {useCallback, useEffect, useRef, useState} from 'react';
2+
import {timerBeepPlayer} from "@/public/audio/AudioManager";
23
import {useInterval} from "@/app/utils/useInterval";
34

4-
export const useCountdown = (
5-
callback: () => void,
6-
duration: number,
7-
delay: number = 50
8-
): [number, () => void, () => void] => {
5+
export const useCountdown = (callback: () => void, duration: number, delay: number = 50): [number, () => void, () => void] => {
96
const savedCallback = useRef<() => void>();
107
const [startTime, setStartTime] = useState<number>();
118

@@ -20,9 +17,16 @@ export const useCountdown = (
2017
const tick = useCallback(() => {
2118
if (startTime === undefined || freeze)
2219
return;
20+
21+
// Calculate the progress
2322
const elapsed = Date.now() - startTime;
2423
const p = Math.max(Math.min(elapsed / duration,1), 0) * 100;
2524

25+
// Play audio each tick
26+
if (p <= 100) {
27+
timerBeepPlayer.play();
28+
}
29+
2630
if (p === 100) {
2731
savedCallback.current?.();
2832
console.log("finished ticks");

audio/Beep.mp3

-6.87 KB
Binary file not shown.

public/audio/AudioManager.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// utils/audioPlayer.ts
2+
3+
export class AudioPlayer {
4+
private audioElement: HTMLAudioElement | null = null;
5+
6+
constructor(src: string, vol: number = 1.0) {
7+
if (typeof window !== 'undefined') {
8+
this.audioElement = new Audio(src);
9+
this.audioElement.preload = 'auto';
10+
}
11+
}
12+
13+
play(): void {
14+
if (this.audioElement) {
15+
this.audioElement.currentTime = 0; // Reset to start
16+
this.audioElement.play().catch(error => {
17+
console.error('Audio playback failed:', error);
18+
});
19+
}
20+
}
21+
}
22+
23+
// Export a specific instance if needed
24+
export const checkBeepPlayer = new AudioPlayer('/audio/check-beep.mp3');
25+
export const timerBeepPlayer = new AudioPlayer('/audio/timer-beep.mp3');
26+
export const successPlayer = new AudioPlayer('/audio/success.mp3');

public/audio/check-beep.mp3

17.4 KB
Binary file not shown.

public/audio/success.mp3

42.9 KB
Binary file not shown.

public/audio/timer-beep.mp3

3.81 KB
Binary file not shown.

0 commit comments

Comments
 (0)