1
1
"use client" ;
2
2
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" ;
4
5
import NPHackContainer from "@/app/components/NPHackContainer" ;
5
6
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" ;
8
8
import usePersistantState from "@/app/utils/usePersistentState" ;
9
9
import { useKeyDown } from "@/app/utils/useKeyDown" ;
10
10
import useGame from "@/app/utils/useGame" ;
11
- import classNames from "classnames" ;
12
- import { clear } from "console" ;
13
- import { generate } from "random-words" ;
14
11
15
12
const defaultDuration = 20 ;
16
13
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
+
17
31
const Pincracker : FC = ( ) => {
18
32
const [ timer , setTimer ] = usePersistantState ( "chopping-timer" , defaultDuration ) ;
19
33
const [ settingsDuration , setSettingsDuration ] = useState ( defaultDuration ) ;
20
34
const [ activeIndex , setActiveIndex ] = usePersistantState ( "pincracker-active-index" , 0 ) ;
21
35
const [ allowKeyDown , setAllowKeyDown ] = useState ( true ) ;
36
+ const [ pinLength , setPinLength ] = useState ( 4 ) ;
22
37
const [ pin , setPin ] = useState < Digit [ ] > ( ) ;
23
38
24
39
const handleCrack = ( ) => {
25
- if ( activeIndex < 4 ) {
40
+ if ( activeIndex < pinLength ) {
26
41
console . log ( 'Incomplete pin' ) ;
27
42
} else {
28
43
const wrappers = document . querySelectorAll ( '.wrapper' ) ;
@@ -31,8 +46,12 @@ const Pincracker: FC = () => {
31
46
const guess = Array . from ( digits ) . map ( d => ( d . innerHTML as Digit ) ) ;
32
47
setAllowKeyDown ( false ) ;
33
48
34
- for ( let i = 0 ; i < 4 ; i ++ ) {
49
+
50
+ for ( let i = 0 ; i < pinLength ; i ++ ) {
35
51
setTimeout ( ( ) => {
52
+ // Play the check beep audio
53
+ checkBeepPlayer . play ( ) ;
54
+
36
55
// Remove the background color of the previous wrapper if it exists
37
56
if ( i > 0 ) {
38
57
wrappers [ i - 1 ] . classList . remove ( 'bg-gradient-radial' , 'from-spring-green-300' , 'to-turquoise-900/50' ) ;
@@ -66,7 +85,6 @@ const Pincracker: FC = () => {
66
85
67
86
setTimeout ( ( ) => {
68
87
if ( pin && guess . join ( '' ) === pin . join ( '' ) ) {
69
- console . log ( 'Success!' ) ;
70
88
setGameStatus ( 3 ) ;
71
89
}
72
90
setActiveIndex ( 0 ) ;
@@ -77,15 +95,15 @@ const Pincracker: FC = () => {
77
95
78
96
const clearBoard = ( delay : number ) => {
79
97
const digits = document . querySelectorAll ( '.digit' ) ;
80
- for ( let i = 3 ; i > - 1 ; i -- ) {
98
+ for ( let i = pinLength - 1 ; i > - 1 ; i -- ) {
81
99
setTimeout ( ( ) => {
82
100
digits [ i ] . innerHTML = '' ;
83
- } , ( 4 - i ) * delay ) ;
101
+ } , ( pinLength - i ) * delay ) ;
84
102
}
85
103
86
104
setTimeout ( ( ) => {
87
105
setAllowKeyDown ( true ) ;
88
- } , delay * 4 ) ;
106
+ } , delay * pinLength ) ;
89
107
}
90
108
91
109
const clearMarkings = ( ) => {
@@ -104,7 +122,7 @@ const Pincracker: FC = () => {
104
122
[ Digits [ i ] , Digits [ j ] ] = [ Digits [ j ] , Digits [ i ] ] ;
105
123
}
106
124
107
- const newPin = Digits . slice ( 0 , 4 ) ;
125
+ const newPin = Digits . slice ( 0 , pinLength ) ;
108
126
setPin ( newPin ) ;
109
127
}
110
128
@@ -121,9 +139,7 @@ const Pincracker: FC = () => {
121
139
switch ( newStatus ) {
122
140
case 1 :
123
141
setAllowKeyDown ( false ) ;
124
- console . log ( 'Reset game' ) ;
125
142
resetBoard ( ) ;
126
-
127
143
break ;
128
144
}
129
145
}
@@ -150,7 +166,7 @@ const Pincracker: FC = () => {
150
166
}
151
167
152
168
else {
153
- if ( activeIndex < 4 ) {
169
+ if ( activeIndex < pinLength ) {
154
170
const digits = document . querySelectorAll ( '.digit' ) ;
155
171
digits [ activeIndex ] . innerHTML = key . toString ( ) ;
156
172
setActiveIndex ( activeIndex + 1 ) ;
@@ -166,10 +182,10 @@ const Pincracker: FC = () => {
166
182
} , [ '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '0' , 'Backspace' , 'Enter' ] ) ;
167
183
168
184
useEffect ( ( ) => {
169
- if ( gameStatus !== 4 ) {
170
- resetGame ( ) ;
185
+ if ( gameStatus === 3 ) {
186
+ successPlayer . play ( ) ;
171
187
}
172
- } , [ timer ] )
188
+ } , [ gameStatus ] )
173
189
174
190
const settings = {
175
191
handleSave : ( ) => {
@@ -184,6 +200,13 @@ const Pincracker: FC = () => {
184
200
185
201
children : (
186
202
< 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
+ />
187
210
< NPSettingsRange
188
211
title = { "Duration (seconds)" }
189
212
min = { 5 }
@@ -197,58 +220,40 @@ const Pincracker: FC = () => {
197
220
198
221
return (
199
222
< 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 }
219
242
>
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 >
252
257
</ NPHackContainer >
253
258
) ;
254
259
}
0 commit comments