Card Rat: play failure audio on missed triggers and add hint visibility toggle#109
Conversation
Agent-Logs-Url: https://github.com/acrosman/BrainSpeedExercises/sessions/cfecd75f-55e3-4a5c-8fa8-6d20f777fcbb Co-authored-by: acrosman <2972053+acrosman@users.noreply.github.com>
acrosman
left a comment
There was a problem hiding this comment.
Please address the following issues:
- I've replaced the sprite image, re-target the selectors for the new card locations.
- Test coverage is insufficient, not all functions are covered.
- The game doesn't reliably respond to the player pressing space. They have to click on the cards at least once first. The key listener should be for more than just the card display area.
Re-review the code, look for other issues and ways to improve the new game's code.
Agent-Logs-Url: https://github.com/acrosman/BrainSpeedExercises/sessions/b2ca2864-c376-4caf-8b9b-b2bc920bb5ab Co-authored-by: acrosman <2972053+acrosman@users.noreply.github.com>
Agent-Logs-Url: https://github.com/acrosman/BrainSpeedExercises/sessions/b2ca2864-c376-4caf-8b9b-b2bc920bb5ab Co-authored-by: acrosman <2972053+acrosman@users.noreply.github.com>
Implemented in
Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Agent-Logs-Url: https://github.com/acrosman/BrainSpeedExercises/sessions/47e9cb64-82f4-4d14-b0b0-34d81d6ebcac Co-authored-by: acrosman <2972053+acrosman@users.noreply.github.com>
…abels Agent-Logs-Url: https://github.com/acrosman/BrainSpeedExercises/sessions/47e9cb64-82f4-4d14-b0b0-34d81d6ebcac Co-authored-by: acrosman <2972053+acrosman@users.noreply.github.com>
Implemented in
Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Agent-Logs-Url: https://github.com/acrosman/BrainSpeedExercises/sessions/fbe721b0-18ee-4bb2-9ef2-de84ea75b3c4 Co-authored-by: acrosman <2972053+acrosman@users.noreply.github.com>
Implemented in I replaced sprite-sheet targeting with generated per-card SVG images (one image per card), including dedicated SVG rendering for jokers and deck back, and tightened the card display sizing/layout to match the rendered cards. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
There was a problem hiding this comment.
Pull request overview
This PR updates the Card Rat game to provide immediate negative feedback when players miss required reaction windows (pair/sandwich/joker), and adds a player-facing control to hide/show the hint text under the cards. It also extends the shared audio service with a synthesized “card flick” sound and adds/updates Jest coverage around the new behaviors.
Changes:
- Added “missed trigger” signaling from
game.dealNextCard()through the UI deal loop, which now plays failure audio on missed reaction windows. - Added a “Show hints” checkbox that toggles visibility of the in-game hint/feedback text without impacting gameplay state.
- Expanded Card Rat and audio service test coverage (including new
playCardFlickSound()coverage).
Reviewed changes
Copilot reviewed 11 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| app/games/card-rat/game.js | Introduces missed-trigger finalization and exposes missedTrigger in the deal contract; adaptive speed tracking. |
| app/games/card-rat/index.js | Wires missed-trigger audio + hint visibility toggle into the UI/controller layer and saveScore integration. |
| app/games/card-rat/interface.html | Adds “Show hints” control and Card Rat UI structure including trend chart and live feedback region. |
| app/games/card-rat/style.css | Adds Card Rat-specific layout/styling for cards, controls, and feedback text. |
| app/games/card-rat/cardSvg.js | Adds sprite/joker/deck-back image helpers for Card Rat rendering. |
| app/games/card-rat/manifest.json | Registers the Card Rat plugin metadata for the game registry. |
| app/games/card-rat/tests/game.test.js | Adds node-environment unit tests for Card Rat core game logic, including missed-trigger behavior. |
| app/games/card-rat/tests/index.test.js | Adds jsdom tests for UI/controller behavior including miss-audio and hint toggling. |
| app/games/card-rat/tests/cardSvg.test.js | Adds unit tests for Card Rat image-path and sprite-style helpers. |
| app/components/audioService.js | Adds playCardFlickSound() (noise swish + snap transient) and supporting constants. |
| app/components/tests/audioService.test.js | Extends audio service mocks and tests to cover playCardFlickSound() and related behaviors. |
Comments suppressed due to low confidence (2)
app/games/card-rat/game.js:467
- The JSDoc for
getSpeedHistory()says the history is “captured after successful reactions”, but the history is appended wheneverapplyStaircaseStep()changes the speed level (including after consecutive misses/false alarms). Update the doc (or adjust the history semantics) so it matches the actual behavior.
/**
* Return display-duration history captured after successful reactions.
* @returns {number[]}
*/
export function getSpeedHistory() {
return speedHistory.slice();
}
app/components/tests/audioService.test.js:216
- There is a
playCardFlickSound()resume-rejection test nested underdescribe('playSuccessSound'), and it duplicates the exact test name “handles resume rejection gracefully”. This makes the suite harder to read/debug. Move theplayCardFlickSoundtest into thedescribe('playCardFlickSound')block and give it a distinct name.
test('handles resume rejection gracefully', async () => {
const { mockCtx, MockAC } = buildMockAudioContext('suspended');
mockCtx.resume = jest.fn().mockRejectedValue(new Error('cannot resume'));
const existing = getAudioContext();
if (existing) existing.state = 'closed';
const original = globalThis.AudioContext;
globalThis.AudioContext = MockAC;
expect(() => playSuccessSound()).not.toThrow();
await new Promise((resolve) => { setTimeout(resolve, 0); });
globalThis.AudioContext = original;
});
test('handles resume rejection gracefully', async () => {
const { mockCtx, MockAC } = buildMockAudioContext('suspended');
mockCtx.resume = jest.fn().mockRejectedValue(new Error('cannot resume'));
const existing = getAudioContext();
if (existing) existing.state = 'closed';
const original = globalThis.AudioContext;
globalThis.AudioContext = MockAC;
expect(() => playCardFlickSound()).not.toThrow();
await new Promise((resolve) => { setTimeout(resolve, 0); });
globalThis.AudioContext = original;
});
| return { | ||
| score, | ||
| triggerHits, | ||
| misses, | ||
| falseAlarms, | ||
| cardsShown, | ||
| deckPasses, | ||
| lowestDisplayTime: displayDurationMs, | ||
| duration, | ||
| }; |
| function stop() { | ||
| clearDealTimer(); | ||
| detachGlobalKeyListener(); | ||
| timerService.stopTimer(); | ||
|
|
||
| if (!game.isRunning()) { | ||
| return { | ||
| score: game.getScore(), | ||
| triggerHits: game.getTriggerHits(), | ||
| misses: game.getMisses(), | ||
| falseAlarms: game.getFalseAlarms(), | ||
| cardsShown: game.getCardsShown(), | ||
| deckPasses: game.getDeckPasses(), | ||
| lowestDisplayTime: game.getDisplayDurationMs(), | ||
| duration: 0, | ||
| }; | ||
| } | ||
|
|
||
| const result = game.stopGame(); | ||
|
|
||
| void saveScore( | ||
| GAME_ID, | ||
| { | ||
| score: result.score, | ||
| sessionDurationMs: result.duration, | ||
| lowestDisplayTime: result.lowestDisplayTime, | ||
| }, |
| aria-label="Display interval trend over recent successful slaps"> | ||
| <polyline id="cr-trend-line" fill="none" stroke="currentColor" stroke-width="2" points=""></polyline> | ||
| </svg> | ||
| <p id="cr-trend-empty" class="game-trend__empty">Get a hit to populate chart.</p> |
| const mockCtx = { | ||
| state, | ||
| currentTime: 0, | ||
| sampleRate: 44100, | ||
| destination: {}, | ||
| createOscillator: jest.fn(() => ({ ...mockOscillator })), | ||
| createGain: jest.fn(() => ({ ...mockGain })), | ||
| createOscillator: jest.fn(() => createMockOscillator()), | ||
| createGain: jest.fn(() => createMockGain()), | ||
| createBuffer: jest.fn(() => ({ getChannelData: jest.fn(() => new Float32Array(1)) })), | ||
| createBufferSource: jest.fn(() => createMockBufferSource()), | ||
| createBiquadFilter: jest.fn(() => createMockBiquadFilter()), |
acrosman
left a comment
There was a problem hiding this comment.
There is a linting error
app/components/tests/audioService.test.js
66:1 error This line has a length of 111. Maximum allowed is 100 max-len
✖ 1 problem (1 error, 0 warnings)
There is also a test coverage gap:
Jest: "global" coverage threshold for functions (100%) not met: 99.84%
Fixed in the latest commit:
|
…sman/BrainSpeedExercises into copilot/add-game-egyptian-rat-screw * 'copilot/add-game-egyptian-rat-screw' of github.com:acrosman/BrainSpeedExercises: Fix lint line-length and add getLowestDisplayTimeMs test coverage





Card Rat previously played failure audio only on false alarms, so missed pair/sandwich/joker windows had no immediate negative feedback. This change adds explicit miss signaling in deal flow and introduces a player control to hide/show the hint text under the cards.
Miss feedback wiring
dealNextCard()now returnsmissedTriggerwhen the prior reaction window closes without a required slap.Hint visibility control
Show hintscheckbox in Card Rat controls.#cr-feedbackwithout affecting gameplay state.Contract + test updates
missedTriggerfield.