Skip to content

Commit 13fbd1a

Browse files
committed
feat: add keyboard shortcuts and hints for RPS, Number Guessing and Hangman
1 parent 06f6bd4 commit 13fbd1a

3 files changed

Lines changed: 130 additions & 11 deletions

File tree

web-app/js/projects/hangman.js

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ function getHangmanHTML() {
2424
</div>
2525
2626
<div class="keyboard" id="keyboard"></div>
27+
28+
<p class="keyboard-hint">⌨️ Type any letter key to guess directly</p>
2729
2830
<div class="game-message" id="gameMessage"></div>
2931
@@ -156,6 +158,11 @@ function getHangmanHTML() {
156158
.key-btn.wrong {
157159
background: var(--danger-color);
158160
}
161+
162+
.key-btn.key-flash {
163+
transform: scale(1.2);
164+
box-shadow: 0 3px 10px rgba(99, 102, 241, 0.6);
165+
}
159166
160167
.game-message {
161168
font-size: 1.5rem;
@@ -171,6 +178,24 @@ function getHangmanHTML() {
171178
.game-message.lose {
172179
color: var(--danger-color);
173180
}
181+
182+
.keyboard-hint {
183+
font-size: 0.9rem;
184+
color: var(--text-secondary);
185+
margin-bottom: 1rem;
186+
}
187+
188+
.keyboard-hint kbd {
189+
display: inline-block;
190+
padding: 0.15rem 0.45rem;
191+
font-size: 0.85rem;
192+
font-family: monospace;
193+
background: var(--surface-color);
194+
border: 1px solid var(--border-color);
195+
border-radius: 5px;
196+
color: var(--primary-color);
197+
font-weight: bold;
198+
}
174199
175200
.btn-new-game {
176201
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
@@ -340,11 +365,16 @@ function initHangman() {
340365
guessedLetters.push(letter);
341366

342367
const btn = keyboard.querySelector(`[data-letter="${letter}"]`);
343-
btn.disabled = true;
368+
if (btn) {
369+
btn.disabled = true;
370+
// Flash the button briefly when triggered by keyboard
371+
btn.classList.add('key-flash');
372+
setTimeout(() => btn.classList.remove('key-flash'), 150);
373+
}
344374

345375
if (currentWord.includes(letter)) {
346376
correctLetters.push(letter);
347-
btn.classList.add('correct');
377+
btn && btn.classList.add('correct');
348378

349379
updateWordDisplay();
350380

@@ -356,7 +386,7 @@ function initHangman() {
356386
}
357387
} else {
358388
wrongAttempts++;
359-
btn.classList.add('wrong');
389+
btn && btn.classList.add('wrong');
360390

361391
const drawStage = wrongAttempts + 4;
362392
drawHangman(drawStage);
@@ -368,7 +398,6 @@ function initHangman() {
368398
gameMessage.innerHTML = `😔 Game Over! The word was: <strong>${currentWord.toUpperCase()}</strong>`;
369399
gameMessage.className = 'game-message lose';
370400
disableAllKeys();
371-
updateWordDisplay();
372401
wordDisplay.innerHTML = '';
373402
for (let letter of currentWord) {
374403
const letterBox = document.createElement('div');
@@ -400,15 +429,28 @@ function initHangman() {
400429
initGame();
401430
drawGallows();
402431
});
403-
404-
document.addEventListener('keypress', (e) => {
432+
433+
// Keyboard handler — using keydown instead of deprecated keypress
434+
function handleKeydown(e) {
405435
if (gameOver) return;
436+
// Ignore if user is typing in an input field
437+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
406438
const letter = e.key.toLowerCase();
407439
if (/^[a-z]$/.test(letter) && !guessedLetters.includes(letter)) {
408440
guessLetter(letter);
409441
}
442+
}
443+
document.addEventListener('keydown', handleKeydown);
444+
445+
// Clean up listener when modal closes
446+
const observer = new MutationObserver(() => {
447+
if (!document.getElementById('newGameBtn')) {
448+
document.removeEventListener('keydown', handleKeydown);
449+
observer.disconnect();
450+
}
410451
});
452+
observer.observe(document.body, { childList: true, subtree: true });
411453

412454
initGame();
413455
drawGallows();
414-
}
456+
}

web-app/js/projects/number-guessing.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ function getNumberGuessingHTML() {
99
<input type="number" id="guessInput" min="1" max="100" placeholder="Enter your guess">
1010
<button class="btn-guess" id="submitGuess">Guess!</button>
1111
</div>
12+
13+
<p class="keyboard-hint">⌨️ Press <kbd>Enter</kbd> to submit your guess</p>
1214
1315
<div class="feedback" id="feedback"></div>
1416
@@ -46,7 +48,7 @@ function getNumberGuessingHTML() {
4648
display: flex;
4749
gap: 1rem;
4850
justify-content: center;
49-
margin-bottom: 2rem;
51+
margin-bottom: 0.75rem;
5052
}
5153
5254
.guess-input-group input {
@@ -78,6 +80,25 @@ function getNumberGuessingHTML() {
7880
cursor: pointer;
7981
font-size: 1.2rem;
8082
}
83+
84+
.keyboard-hint {
85+
font-size: 0.9rem;
86+
color: var(--text-secondary);
87+
margin-bottom: 1rem;
88+
}
89+
90+
.keyboard-hint kbd {
91+
display: inline-block;
92+
padding: 0.15rem 0.45rem;
93+
font-size: 0.85rem;
94+
font-family: monospace;
95+
background: var(--surface-color);
96+
border: 1px solid var(--border-color);
97+
border-radius: 5px;
98+
color: var(--primary-color);
99+
font-weight: bold;
100+
}
101+
81102
.feedback {
82103
font-size: 1.5rem;
83104
font-weight: bold;
@@ -128,8 +149,11 @@ function initNumberGuessing() {
128149
let bestScore = storage.loadFromStorage('numberGuessBest', null);
129150
updateBestScoreDisplay();
130151

152+
// Focus input automatically so Enter works right away
153+
guessInput.focus();
154+
131155
submitBtn.addEventListener('click', makeGuess);
132-
guessInput.addEventListener('keypress', (e) => {
156+
guessInput.addEventListener('keydown', (e) => {
133157
if (e.key === 'Enter') makeGuess();
134158
});
135159

@@ -144,6 +168,7 @@ function initNumberGuessing() {
144168
guessInput.value = '';
145169
guessInput.disabled = false;
146170
submitBtn.disabled = false;
171+
guessInput.focus();
147172
});
148173

149174
function updateBestScoreDisplay() {
@@ -189,4 +214,4 @@ function initNumberGuessing() {
189214
rangeDisplay.textContent = `${minRange}-${maxRange}`;
190215
guessInput.value = '';
191216
}
192-
}
217+
}

web-app/js/projects/rock-paper-scissor.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ function getRockPaperScissorHTML() {
8383
<span>Scissors</span>
8484
</button>
8585
</div>
86+
87+
<p class="keyboard-hint">⌨️ Press <kbd>R</kbd> Rock · <kbd>P</kbd> Paper · <kbd>S</kbd> Scissors</p>
8688
8789
<button class="btn-reset" id="resetRPS">Reset Game</button>
8890
</div>
@@ -235,10 +237,34 @@ function getRockPaperScissorHTML() {
235237
border-color: var(--primary-color);
236238
box-shadow: 0 5px 20px rgba(99, 102, 241, 0.3);
237239
}
240+
241+
.choice-btn.key-active {
242+
transform: translateY(-5px);
243+
border-color: var(--primary-color);
244+
box-shadow: 0 5px 20px rgba(99, 102, 241, 0.3);
245+
}
238246
239247
.choice-icon {
240248
font-size: 3rem;
241249
}
250+
251+
.keyboard-hint {
252+
font-size: 0.9rem;
253+
color: var(--text-secondary);
254+
margin-bottom: 1rem;
255+
}
256+
257+
.keyboard-hint kbd {
258+
display: inline-block;
259+
padding: 0.15rem 0.45rem;
260+
font-size: 0.85rem;
261+
font-family: monospace;
262+
background: var(--surface-color);
263+
border: 1px solid var(--border-color);
264+
border-radius: 5px;
265+
color: var(--primary-color);
266+
font-weight: bold;
267+
}
242268
243269
.btn-reset {
244270
background: var(--danger-color);
@@ -271,6 +297,7 @@ function initRockPaperScissor() {
271297

272298
const choices = ['rock', 'paper', 'scissors'];
273299
const emojis = { rock: '🪨', paper: '📄', scissors: '✂️' };
300+
const keyMap = { r: 'rock', p: 'paper', s: 'scissors' };
274301

275302
const storage = window.appStorage || {
276303
saveToStorage(key, value) {
@@ -315,6 +342,22 @@ function initRockPaperScissor() {
315342
playRound(playerChoice);
316343
});
317344
});
345+
346+
// Keyboard shortcut handler
347+
function handleKeydown(e) {
348+
const key = e.key.toLowerCase();
349+
if (keyMap[key]) {
350+
const choice = keyMap[key];
351+
// Briefly highlight the matching button
352+
const matchingBtn = document.querySelector(`.choice-btn[data-choice="${choice}"]`);
353+
if (matchingBtn) {
354+
matchingBtn.classList.add('key-active');
355+
setTimeout(() => matchingBtn.classList.remove('key-active'), 200);
356+
}
357+
playRound(choice);
358+
}
359+
}
360+
document.addEventListener('keydown', handleKeydown);
318361

319362
resetBtn.addEventListener('click', () => {
320363
playerScore = 0;
@@ -333,6 +376,15 @@ function initRockPaperScissor() {
333376
});
334377
});
335378

379+
// Clean up keydown listener when modal closes
380+
const observer = new MutationObserver(() => {
381+
if (!document.getElementById('resetRPS')) {
382+
document.removeEventListener('keydown', handleKeydown);
383+
observer.disconnect();
384+
}
385+
});
386+
observer.observe(document.body, { childList: true, subtree: true });
387+
336388
function updateStatsDisplay() {
337389
gamesPlayedDisplay.textContent = stats.gamesPlayed;
338390
winsDisplay.textContent = stats.wins;
@@ -398,4 +450,4 @@ function initRockPaperScissor() {
398450
document.getElementById('playerScore').textContent = playerScore;
399451
document.getElementById('computerScore').textContent = computerScore;
400452
}
401-
}
453+
}

0 commit comments

Comments
 (0)