Skip to content

Add new brain game: Bullet Math #115

Closed
aleenaharoldpeter wants to merge 8 commits into
ajaynegi33:mainfrom
aleenaharoldpeter:feature/bullet-math
Closed

Add new brain game: Bullet Math #115
aleenaharoldpeter wants to merge 8 commits into
ajaynegi33:mainfrom
aleenaharoldpeter:feature/bullet-math

Conversation

@aleenaharoldpeter

Copy link
Copy Markdown
Contributor

🧮 New Game: Bullet Math

This PR adds a new brain game Bullet Math under src/app/game/bullet-math/.


✨ Features

  • 3 difficulty levels:
    • Amateur → single-digit operations
    • Normal → two-digit operations and mid-level multiplication/division
    • Veteran → multi-digit, 3×2 style multiplication, and challenging division
  • Timer-based gameplay (2 minutes per session).
  • Score & attempted questions tracking.
  • End-game popup with:
    • 🎉 Time’s Up message
    • ⭐ Final score
    • 📊 Questions attempted
    • 🔄 Play Again button (resets game immediately)
    • 🏠 Back to Homepage button
  • Math Hack hints for Veteran mode (e.g., breaking down 3×2 multiplication, compensation tricks).

🛠️ Fixes/Changes

  • Updated src/app/game/page.tsx to include Bullet Math entry in the game selection.
  • Added new folder: src/app/game/bullet-math/ containing:
    • page.tsx → Game logic
    • page.module.css → Styling
  • Refactored timer logic (removed any, used ReturnType<typeof setInterval>).
  • Corrected “Questions Attempted” counter to reflect all answered questions (not just correct ones).
  • Improved accessibility:
    • Added aria-label to input
    • Explicit type="button" on buttons
    • Overlay marked as role="dialog" with aria-labelledby
    • Added autoFocus to input and reset button
    • Added keyboard :focus-visible outline styles

✅ Checklist

  • Linked to homepage game list
  • Added new game code
  • Tested locally with all difficulty levels
  • End-game popup flow verified
  • Accessibility + type issues fixed

🙏 Acknowledgements

  • Fixed issues raised in PR Add new brain game: Bullet Math #112 (Ajay’s review: no-explicit-any ESLint error).
  • Fixed issues raised in PR Add new brain game: Bullet Math #113 :
    • Removed duplicate CSS rules (.input, .menuButton).
    • Refactored timer cleanup.
    • Corrected attempted counter logic.
    • Removed undefined .startBtn usage.
    • Added autoFocus and focus-ring styles for accessibility.
    • Improved accessibility for inputs, buttons, and overlays.

This PR introduces a fresh math challenge game to the project 🚀

@vercel

vercel Bot commented Sep 5, 2025

Copy link
Copy Markdown

@aleenaharoldpeter is attempting to deploy a commit to the ajaynegi45's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Sep 5, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added Bullet Math game: a 2-minute arithmetic challenge (+, −, ×, ÷) with Amateur/Normal/Veteran modes, scoring, attempts tracking, and integer-only division.
    • Veteran mode provides contextual hints.
    • Streamlined flow: Enter-to-submit, input focus management, and end-of-round overlay with final score, attempts, and replay/home actions.
    • Added Bullet Math card to the game selection grid.
  • Style

    • Introduced responsive, accessible styling: layout, large equation display, color-coded buttons, inputs with error state, status bar, overlay/modal, hint/info boxes, and focus-visible outlines.

Walkthrough

Adds a new Bullet Math client page and CSS module implementing a 120s timed arithmetic game (three difficulties, + − × ÷ with integer-safe division), scoring, veteran hints, input/submit flow, end-of-game modal, and a new game card on the game selection page.

Changes

Cohort / File(s) Summary
Bullet Math UI Styles
src/app/game/bullet-math/page.module.css
New CSS module defining layout, equation display, button variants (.true/.false/.neutral/.wrong), inputs, menu/reset buttons, overlay modal, hint/info boxes, responsive rules (<400px), and focus-visible accessibility styles.
Bullet Math Game Page
src/app/game/bullet-math/page.tsx
New client-side React component (default export) implementing timed gameplay: difficulty radio selectors, equation generation (integer-safe division), scoring by difficulty, veteran hints, input handling (Enter to submit), timer via useEffect with cleanup, focus management, replay/reset, and end-of-game overlay.
Game Selection Update
src/app/game/page.tsx
Adds Bullet Math card to the selection grid (imports /public/BulletMath.png, link bullet-math, background and text color metadata).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Page as BulletMath Page
  participant Timer as Countdown Interval
  participant Router as App Router

  User->>Page: Open /game/bullet-math
  Page-->>User: Show difficulty options, Start button
  User->>Page: Click Start
  Page->>Timer: start 120s interval
  Page->>Page: generateEquation(), focus input

  loop while time > 0
    User->>Page: Type answer + Enter
    alt correct
      Page->>Page: increment score, attempts, generate next equation
      Page-->>User: show next equation
    else incorrect
      Page->>Page: mark input wrong, count attempt
      Page-->>User: show wrong state
    end
    opt veteran mode
      Page-->>User: display hint
    end
  end

  Timer-->>Page: time expired
  Page->>Timer: clear interval
  Page-->>User: show overlay (final stats, Replay/Home)
  User->>Router: Replay or Navigate Home
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • [NEW GAME] Bullet Math #111 — Implements Bullet Math generation, scoring, timer, input handling, and veteran hints that align with this PR's changes; likely the same requested feature.

Poem

Numbers sprint in sixty pairs,
Timers hum and challenge dares.
Hints like lanterns guide the way,
Three modes chase the racing play.
Score, replay — the sums declare 🎯

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Sep 5, 2025

Copy link
Copy Markdown

👋 Hello @aleenaharoldpeter!

Thanks for contributing to Old-School-Game – a curated collection of classic, brain-training games that boost memory, focus, creativity, and long-term brain health 🎯🧠.

In today's world full of short-form distractions, this project helps rebuild deep focus and mental endurance through thoughtful gameplay.


✅ Important Things To Do:

  • 🌟 Don’t forget to ⭐ Star the repo and follow @ajaynegi45 for updates, ideas, and dev insights.
  • 📬 Stay inspired! Sign up for the monthly CodiesCoder Newsletter – we explore one key software engineering topic each month, beginner-friendly and fun to read!

✅ Your pull request has been submitted successfully, and a maintainer will review it soon.

@coderabbitai coderabbitai Bot added enhancement New feature or request level3 size:XL This PR changes 500-999 lines, ignoring generated files labels Sep 5, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 486c828 and 490a6d5.

⛔ Files ignored due to path filters (1)
  • public/BulletMath.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • src/app/game/bullet-math/page.module.css (1 hunks)
  • src/app/game/bullet-math/page.tsx (1 hunks)
  • src/app/game/page.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/app/game/page.tsx (1)
src/app/game/bullet-math/page.tsx (1)
  • BulletMath (116-274)
src/app/game/bullet-math/page.tsx (1)
src/app/game/schulte-table/page.tsx (2)
  • numbers (14-185)
  • startNewGame (45-54)
🪛 Biome (2.1.2)
src/app/game/bullet-math/page.tsx

[error] 238-238: Avoid the autoFocus attribute.

Unsafe fix: Remove the autoFocus attribute.

(lint/a11y/noAutofocus)


[error] 243-243: Avoid the autoFocus attribute.

Unsafe fix: Remove the autoFocus attribute.

(lint/a11y/noAutofocus)

🔇 Additional comments (3)
src/app/game/page.tsx (2)

49-49: Card integration looks good.

Props match the existing pattern; link resolves to /game/bullet-math.


8-8: Confirm and correct BulletMath import path
public/BulletMath.png exists (case-matched). Change the import to

import BulletMath from "/BulletMath.png"

and verify that your Card component’s props accept a StaticImageData import (otherwise pass the URL string).

src/app/game/bullet-math/page.tsx (1)

176-197: Attempt counter behavior: confirm intended semantics.

You count “attempted” once per equation (first try only). If the goal is “questions answered” (regardless of multiple wrong tries), this is correct. If you meant “number of submissions,” this undercounts.

Comment on lines +23 to +34
.equationBox {
font-size: 36px;
font-weight: 700;
padding: 20px 32px;
border-radius: 12px;
border: 2px solid #ddd;
min-width: 360px;
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Responsive min-width could still overflow on very narrow devices.

You already adjust under 400px; consider using min(360px, 100%) to avoid any overflow without the media query dependency.

-.equationBox {
+.equationBox {
   font-size: 36px;
@@
-  min-width: 360px;
+  min-width: min(360px, 100%);

Also applies to: 143-159

🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.module.css around lines 23 to 34 (and similarly
apply the same change to lines 143-159), the fixed min-width: 360px can still
cause horizontal overflow on very narrow screens; replace the hard min-width
with a responsive constraint like min-width: min(360px, 100%) (or use width:
min(360px, 100%)) so the element never exceeds the viewport width while
preserving the intended minimum on larger screens.

Comment on lines +51 to +54
.true { background: #16a34a; color: white; }
.false { background: #dc2626; color: white; }
.neutral { background: #334155; color: white; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Unused variants?

If .true/.false aren’t used, drop them to reduce CSS surface. Keep only what’s referenced in JSX.

🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.module.css around lines 51 to 54, the CSS
defines .true and .false classes that may be unused; remove those class rules if
they are not referenced in any JSX/TSX to reduce CSS surface, or alternatively
verify and rename/replace them with the actual class names used in components
(or convert to a single reusable utility class) and update the JSX to reference
the kept class; ensure you run a quick grep/IDE search for ".true" and ".false"
usages before deleting to avoid breaking references.

Comment on lines +103 to +121
.overlay {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.7);
display: flex; align-items: center; justify-content: center;
z-index: 1000;
}

.completionMessage {
background: white;
padding: 30px;
border-radius: 16px;
text-align: center;
max-width: 400px;
width: 90%;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Modal styles are solid; consider motion-reduction for hover animations.

Respect users who prefer reduced motion by disabling hover transforms.

 @media (max-width: 400px) {
   .equationBox {
@@
   }
 }
+
+@media (prefers-reduced-motion: reduce) {
+  .menuButton:hover,
+  .resetButton:hover {
+    transform: none;
+  }
+}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.module.css around lines 103-121, add a
prefers-reduced-motion rule so users who prefer reduced motion won't see
hover/animation transforms: create an @media (prefers-reduced-motion: reduce)
block that sets transition: none; animation: none; and transform: none on
.overlay and .completionMessage (and any related hover/focus selectors used
elsewhere), so hover transforms and animations are disabled for those users.

Comment thread src/app/game/bullet-math/page.tsx Outdated
Comment thread src/app/game/bullet-math/page.tsx
Comment thread src/app/game/bullet-math/page.tsx Outdated
Comment thread src/app/game/bullet-math/page.tsx Outdated
@coderabbitai coderabbitai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Sep 5, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
src/app/game/bullet-math/page.tsx (3)

25-36: Exhaustive switch on Op — nice and safe.

The default+never pattern makes future Op additions fail fast at compile-time. Good guardrail.


163-174: Managed focus looks good.

Nice job replacing autoFocus with refs/effects and moving focus to Start/Play Again appropriately.

Also applies to: 241-247, 266-273


220-224: Polish: make status updates fully screen-reader friendly.

Add role="status" and aria-atomic="true" so updates are announced cleanly.

-      <div className={styles.statusBar} aria-live="polite">
+      <div className={styles.statusBar} role="status" aria-live="polite" aria-atomic="true">
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 490a6d5 and f13cd32.

📒 Files selected for processing (1)
  • src/app/game/bullet-math/page.tsx (1 hunks)

Comment on lines +128 to +129
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
const inputRef = useRef<HTMLInputElement>(null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove unsafe casts when clearing the timer; use window.setInterval/clearInterval and null the ref.

The as unknown as number casts are brittle. Since this is a client component, prefer window.setInterval/window.clearInterval, and always null the ref after clearing to prevent double-clears or leaks.

Apply this diff:

-  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
+  const timerRef = useRef<number | null>(null)
@@
-    timerRef.current = setInterval(() => {
+    timerRef.current = window.setInterval(() => {
       setTimeLeft(t => {
         if (t <= 1) {
-          clearInterval(timerRef.current as unknown as number)
+          if (timerRef.current != null) {
+            window.clearInterval(timerRef.current)
+            timerRef.current = null
+          }
           setRunning(false)
           setGameOver(true)
           return 0
         }
         return t - 1
       })
     }, 1000)
     return () => {
-      if (timerRef.current) clearInterval(timerRef.current as unknown as number)
+      if (timerRef.current != null) {
+        window.clearInterval(timerRef.current)
+        timerRef.current = null
+      }
     }

Also applies to: 147-161

🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.tsx around lines 128-129 and also affecting
147-161, replace the unsafe casts when storing/clearing intervals by using
browser timer types and window helpers: change the timer ref to hold number |
null (e.g. useRef<number | null>(null)), use window.setInterval to assign a
numeric id to timerRef.current, use window.clearInterval(timerRef.current) when
clearing (guarding for non-null), and then set timerRef.current = null after
clearing to avoid double-clears and leaks; remove any "as unknown as number"
casts.

Comment on lines +206 to +217
<div className={styles.controls}>
<div className={styles.radioGroup}>
<label>
<input type="radio" checked={difficulty === 'amateur'} onChange={() => setDifficulty('amateur')} disabled={running}/> Amateur
</label>
<label>
<input type="radio" checked={difficulty === 'normal'} onChange={() => setDifficulty('normal')} disabled={running}/> Normal
</label>
<label>
<input type="radio" checked={difficulty === 'veteran'} onChange={() => setDifficulty('veteran')} disabled={running}/> Veteran
</label>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Group radios with a shared name and mark the container as a radiogroup.

Without a name, screen readers may not treat these as one group. Adding role="radiogroup" and a label improves a11y and keyboard UX.

-      <div className={styles.controls}>
-        <div className={styles.radioGroup}>
+      <div className={styles.controls}>
+        <div className={styles.radioGroup} role="radiogroup" aria-label="Difficulty">
           <label>
-            <input type="radio" checked={difficulty === 'amateur'} onChange={() => setDifficulty('amateur')} disabled={running}/> Amateur
+            <input name="difficulty" type="radio" checked={difficulty === 'amateur'} onChange={() => setDifficulty('amateur')} disabled={running}/> Amateur
           </label>
           <label>
-            <input type="radio" checked={difficulty === 'normal'} onChange={() => setDifficulty('normal')} disabled={running}/> Normal
+            <input name="difficulty" type="radio" checked={difficulty === 'normal'} onChange={() => setDifficulty('normal')} disabled={running}/> Normal
           </label>
           <label>
-            <input type="radio" checked={difficulty === 'veteran'} onChange={() => setDifficulty('veteran')} disabled={running}/> Veteran
+            <input name="difficulty" type="radio" checked={difficulty === 'veteran'} onChange={() => setDifficulty('veteran')} disabled={running}/> Veteran
           </label>
         </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className={styles.controls}>
<div className={styles.radioGroup}>
<label>
<input type="radio" checked={difficulty === 'amateur'} onChange={() => setDifficulty('amateur')} disabled={running}/> Amateur
</label>
<label>
<input type="radio" checked={difficulty === 'normal'} onChange={() => setDifficulty('normal')} disabled={running}/> Normal
</label>
<label>
<input type="radio" checked={difficulty === 'veteran'} onChange={() => setDifficulty('veteran')} disabled={running}/> Veteran
</label>
</div>
<div className={styles.controls}>
<div className={styles.radioGroup} role="radiogroup" aria-label="Difficulty">
<label>
<input
name="difficulty"
type="radio"
checked={difficulty === 'amateur'}
onChange={() => setDifficulty('amateur')}
disabled={running}
/> Amateur
</label>
<label>
<input
name="difficulty"
type="radio"
checked={difficulty === 'normal'}
onChange={() => setDifficulty('normal')}
disabled={running}
/> Normal
</label>
<label>
<input
name="difficulty"
type="radio"
checked={difficulty === 'veteran'}
onChange={() => setDifficulty('veteran')}
disabled={running}
/> Veteran
</label>
</div>
🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.tsx around lines 206 to 217, the three radio
inputs are missing a shared name and the container lacks radiogroup semantics;
update the container to include role="radiogroup" and an accessible label
(either aria-label or aria-labelledby) and give each input the same name value
(e.g., name="difficulty") so assistive tech and keyboard navigation treat them
as one group; also ensure each label is associated with its input via for/id or
by wrapping as currently done so association remains intact.

Comment on lines +231 to +239
type="text"
value={userAnswer}
onChange={(e) => setUserAnswer(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') onSubmitAnswer() }}
className={`${styles.input} ${isWrong ? styles.wrong : ''}`}
aria-label="Your answer"
placeholder="?"
ref={inputRef}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Clear the red error state as soon as the user edits.

Resetting isWrong on input change gives instant feedback and reduces distraction.

-              onChange={(e) => setUserAnswer(e.target.value)}
+              onChange={(e) => {
+                setUserAnswer(e.target.value)
+                if (isWrong) setIsWrong(false)
+              }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type="text"
value={userAnswer}
onChange={(e) => setUserAnswer(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') onSubmitAnswer() }}
className={`${styles.input} ${isWrong ? styles.wrong : ''}`}
aria-label="Your answer"
placeholder="?"
ref={inputRef}
/>
type="text"
value={userAnswer}
onChange={(e) => {
setUserAnswer(e.target.value)
if (isWrong) setIsWrong(false)
}}
onKeyDown={(e) => { if (e.key === 'Enter') onSubmitAnswer() }}
className={`${styles.input} ${isWrong ? styles.wrong : ''}`}
aria-label="Your answer"
placeholder="?"
ref={inputRef}
/>
🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.tsx around lines 231 to 239, the input change
handler should clear the red error state immediately; update the onChange
callback to call setUserAnswer(e.target.value) and also setIsWrong(false) (or
reset the wrong flag) when the user types so the red styling is removed as soon
as they edit, preserving the existing onKeyDown and other props.

Comment on lines +258 to +274
{gameOver && (
<div className={styles.overlay} role="dialog" aria-modal="true" aria-labelledby="times-up-heading">
<div className={styles.completionMessage}>
<h2 id="times-up-heading">🎉 Time’s Up!</h2>
<div className={styles.finalStats}>
<p>⭐ Score: {score}</p>
<p>📊 Questions Attempted: {attempted}</p>
</div>
<button type="button" className={styles.resetButton} onClick={startGame} ref={resetRef}>
🔄 Play Again
</button>
<button type="button" className={styles.menuButton} onClick={() => router.push('/')}>
🏠 Back to Homepage
</button>
</div>
</div>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Optional: trap focus inside the end-game dialog.

Right now, Tab can escape the modal. Consider a small focus trap (e.g., focus-trap-react) or sentinel nodes to keep keyboard focus within the dialog until it closes.

Happy to provide a minimal trap snippet if you want it inline without a dependency.

🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.tsx around lines 258-274 the end-game dialog
renders but does not trap keyboard focus, allowing Tab to escape the modal; add
a focus trap so keyboard users remain inside while gameOver is true — either
install and wrap the dialog with a light focus-trap-react <FocusTrap> (activate
only when gameOver) or implement sentinel nodes and keydown handlers: on open
move focus to the first focusable control (reset button), handle Tab and
Shift+Tab to cycle between focusable elements, and restore focus when the dialog
closes; ensure the trap is mounted only for the modal and that focusable
elements have refs so the trap can manage focus.

@vercel

vercel Bot commented Sep 6, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
time-pass Error Error Sep 7, 2025 10:29am

@coderabbitai coderabbitai Bot added gssoc GirlScript Summer Of Code gssoc25 labels Sep 6, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (8)
src/app/game/bullet-math/page.module.css (4)

23-34: Prevent overflow on small screens: use a responsive width cap.

Replace fixed min-width with a responsive cap so it never overflows narrow devices.

 .equationBox {
   font-size: 36px;
   font-weight: 700;
   padding: 20px 32px;
   border-radius: 12px;
   border: 2px solid #ddd;
-  min-width: 360px;
+  width: min(360px, 100%);
   display: flex;
   justify-content: center;
   align-items: center;
   gap: 12px;
 }

143-149: Media query override no longer needed if you adopt width:min(360px,100%).

If you take the change above, this override becomes redundant and can be removed.

 @media (max-width: 400px) {
   .equationBox {
-    min-width: auto;
     font-size: 24px;
     padding: 12px 16px;
   }

98-101: Respect reduced-motion preferences for hover effects.

Disable transforms/transitions for users who prefer reduced motion.

 .menuButton:hover {
   background: linear-gradient(135deg, #2563eb, #1e40af);
   transform: translateY(-2px);
 }
+
+@media (prefers-reduced-motion: reduce) {
+  .menuButton,
+  .resetButton {
+    transition: none;
+  }
+  .menuButton:hover,
+  .resetButton:hover {
+    transform: none;
+  }
+}

Also applies to: 127-141


51-54: Remove unused variants to slim CSS.

.true/.false don’t appear to be used in the TSX. If confirmed unused, delete them.

#!/bin/bash
# Verify unused CSS module class usages
rg -nP "styles\.(true|false)\b|['\"](true|false)['\"]" --type=ts --type=tsx -C1
src/app/game/bullet-math/page.tsx (4)

129-161: Safer timer typing and cleanup; remove brittle casts and always null the ref.

Use window.setInterval/clearInterval with a numeric id and null the ref after clearing to avoid double-clears/leaks.

-  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
+  const timerRef = useRef<number | null>(null)
@@
-    timerRef.current = setInterval(() => {
+    timerRef.current = window.setInterval(() => {
       setTimeLeft(t => {
         if (t <= 1) {
-          clearInterval(timerRef.current as unknown as number)
+          if (timerRef.current != null) {
+            window.clearInterval(timerRef.current)
+            timerRef.current = null
+          }
           setRunning(false)
           setGameOver(true)
           return 0
         }
         return t - 1
       })
     }, 1000)
     return () => {
-      if (timerRef.current) clearInterval(timerRef.current as unknown as number)
+      if (timerRef.current != null) {
+        window.clearInterval(timerRef.current)
+        timerRef.current = null
+      }
     }

231-244: Add radiogroup semantics and a shared name for radios.

Improves screen reader grouping and keyboard UX.

-      <div className={styles.controls}>
-        <div className={styles.radioGroup}>
+      <div className={styles.controls}>
+        <div className={styles.radioGroup} role="radiogroup" aria-label="Difficulty">
           <label>
-            <input type="radio" checked={difficulty === 'amateur'} onChange={() => setDifficulty('amateur')} disabled={running}/> Amateur
+            <input name="difficulty" type="radio" checked={difficulty === 'amateur'} onChange={() => setDifficulty('amateur')} disabled={running}/> Amateur
           </label>
           <label>
-            <input type="radio" checked={difficulty === 'normal'} onChange={() => setDifficulty('normal')} disabled={running}/> Normal
+            <input name="difficulty" type="radio" checked={difficulty === 'normal'} onChange={() => setDifficulty('normal')} disabled={running}/> Normal
           </label>
           <label>
-            <input type="radio" checked={difficulty === 'veteran'} onChange={() => setDifficulty('veteran')} disabled={running}/> Veteran
+            <input name="difficulty" type="radio" checked={difficulty === 'veteran'} onChange={() => setDifficulty('veteran')} disabled={running}/> Veteran
           </label>
         </div>

257-266: Better mobile keypad + clear error state on edit.

Surface a numeric keypad and remove the red state as soon as the user types.

             <input
               type="text"
               value={userAnswer}
-              onChange={(e) => setUserAnswer(e.target.value)}
+              onChange={(e) => {
+                setUserAnswer(e.target.value)
+                if (isWrong) setIsWrong(false)
+              }}
               onKeyDown={(e) => { if (e.key === 'Enter') onSubmitAnswer() }}
               className={`${styles.input} ${isWrong ? styles.wrong : ''}`}
               aria-label="Your answer"
               placeholder="?"
               ref={inputRef}
+              inputMode="numeric"
+              pattern="-?[0-9]*"
             />

285-301: Optional: trap focus inside the end-game dialog.

Right now Tab can escape the modal. Consider a small focus trap (e.g., focus-trap-react) or sentinels until it closes. Happy to provide a minimal snippet if you want it dependency-free.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f13cd32 and af4affd.

📒 Files selected for processing (2)
  • src/app/game/bullet-math/page.module.css (1 hunks)
  • src/app/game/bullet-math/page.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/game/bullet-math/page.tsx (2)
src/app/game/memory-flip-card/page.tsx (1)
  • difficulty (47-364)
src/app/game/schulte-table/page.tsx (2)
  • numbers (14-185)
  • startNewGame (45-54)

Comment thread src/app/game/bullet-math/page.module.css Outdated
Comment on lines +84 to +96
.menuButton {
margin-top: 12px;
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
border: none;
padding: 12px 24px;
font-size: 1.1rem;
font-weight: bold;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Micro-optimization: limit transitioned properties.

Avoid transition: all to reduce layout work; scope to the properties you animate.

 .menuButton {
   ...
-  transition: all 0.3s ease;
+  transition: transform 0.2s ease, background 0.2s ease;
   box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.menuButton {
margin-top: 12px;
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
border: none;
padding: 12px 24px;
font-size: 1.1rem;
font-weight: bold;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.menuButton {
margin-top: 12px;
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
border: none;
padding: 12px 24px;
font-size: 1.1rem;
font-weight: bold;
border-radius: 10px;
cursor: pointer;
transition: transform 0.2s ease, background 0.2s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.module.css around lines 84 to 96, the rule uses
"transition: all 0.3s ease" which is broad and causes extra layout work; replace
it with targeted transitions for only the properties you animate (for example:
background-color, transform, box-shadow, color) and keep the same duration and
easing so the visual effect is preserved while reducing repaint/reflow cost.

Comment on lines +185 to +191
const getPointsForDifficulty = (diff: Difficulty) => {
switch (diff) {
case 'amateur': return 1
case 'normal': return 2
case 'veteran': return 3
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Make difficulty-to-points switch exhaustive.

Mirror the exhaustive pattern used in compute so future difficulty additions fail fast.

   const getPointsForDifficulty = (diff: Difficulty) => {
     switch (diff) {
       case 'amateur': return 1
       case 'normal': return 2
       case 'veteran': return 3
+      default: {
+        const _exhaustive: never = diff
+        return _exhaustive
+      }
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getPointsForDifficulty = (diff: Difficulty) => {
switch (diff) {
case 'amateur': return 1
case 'normal': return 2
case 'veteran': return 3
}
}
const getPointsForDifficulty = (diff: Difficulty) => {
switch (diff) {
case 'amateur': return 1
case 'normal': return 2
case 'veteran': return 3
default: {
const _exhaustive: never = diff
return _exhaustive
}
}
}
🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.tsx around lines 185 to 191, the
getPointsForDifficulty switch is non‑exhaustive; update it to mirror the
exhaustive pattern used in compute by handling all Difficulty possibilities
explicitly and adding a default (or a never/assertUnreachable) branch that
throws an error so that adding a new Difficulty will fail at compile/run time;
ensure the function still returns a number and use the same assertUnreachable
helper or throw new Error('Unhandled difficulty: ' + diff) in the default
branch.

Comment thread src/app/game/bullet-math/page.tsx
Comment thread src/app/game/bullet-math/page.tsx
Comment on lines +247 to +251
<div className={styles.statusBar} aria-live="polite">
<span>⏳ {timeLeft}s</span>
<span>⭐ {score}</span>
<span>📊 {attempted} attempted</span>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Announce updates more reliably.

Add role and atomic to ensure concise announcements of time/score/attempts.

-      <div className={styles.statusBar} aria-live="polite">
+      <div className={styles.statusBar} role="status" aria-live="polite" aria-atomic="true">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className={styles.statusBar} aria-live="polite">
<span>{timeLeft}s</span>
<span>{score}</span>
<span>📊 {attempted} attempted</span>
</div>
<div className={styles.statusBar} role="status" aria-live="polite" aria-atomic="true">
<span>{timeLeft}s</span>
<span>{score}</span>
<span>📊 {attempted} attempted</span>
</div>
🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.tsx around lines 247 to 251, the status bar
uses aria-live="polite" but lacks explicit role and atomic settings; update the
div to include role="status" and aria-atomic="true" (keeping aria-live="polite")
so screen readers announce the time/score/attempts as a single, concise update.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (11)
src/app/game/bullet-math/page.tsx (5)

130-134: Timer typing/cleanup: remove unsafe casts and always null the ref.

Use window.setInterval/clearInterval and a numeric ref to avoid brittle casts and double-clears. This also aligns with client-only execution.

-  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
+  const timerRef = useRef<number | null>(null)
@@
-    timerRef.current = setInterval(() => {
+    timerRef.current = window.setInterval(() => {
       setTimeLeft(t => {
         if (t <= 1) {
-          clearInterval(timerRef.current as unknown as number)
+          if (timerRef.current != null) {
+            window.clearInterval(timerRef.current)
+            timerRef.current = null
+          }
           setRunning(false)
           setGameOver(true)
           return 0
         }
         return t - 1
       })
     }, 1000)
     return () => {
-      if (timerRef.current) clearInterval(timerRef.current as unknown as number)
+      if (timerRef.current != null) {
+        window.clearInterval(timerRef.current)
+        timerRef.current = null
+      }
     }

Also applies to: 147-163


186-192: Make difficulty-to-points switch exhaustive.

Small safety win like you did for Op.

   const getPointsForDifficulty = (diff: Difficulty) => {
     switch (diff) {
       case 'amateur': return 1
       case 'normal': return 2
       case 'veteran': return 3
+      default: {
+        const _exhaustive: never = diff
+        return _exhaustive
+      }
     }
   }

235-246: Group radios and add radiogroup semantics.

Improves screen reader announcements and keyboard UX.

-      <div className={styles.controls}>
-        <div className={styles.radioGroup}>
+      <div className={styles.controls}>
+        <div className={styles.radioGroup} role="radiogroup" aria-label="Difficulty">
           <label>
-            <input type="radio" checked={difficulty === 'amateur'} onChange={() => setDifficulty('amateur')} disabled={running}/> Amateur
+            <input name="difficulty" type="radio" checked={difficulty === 'amateur'} onChange={() => setDifficulty('amateur')} disabled={running}/> Amateur
           </label>
           <label>
-            <input type="radio" checked={difficulty === 'normal'} onChange={() => setDifficulty('normal')} disabled={running}/> Normal
+            <input name="difficulty" type="radio" checked={difficulty === 'normal'} onChange={() => setDifficulty('normal')} disabled={running}/> Normal
           </label>
           <label>
-            <input type="radio" checked={difficulty === 'veteran'} onChange={() => setDifficulty('veteran')} disabled={running}/> Veteran
+            <input name="difficulty" type="radio" checked={difficulty === 'veteran'} onChange={() => setDifficulty('veteran')} disabled={running}/> Veteran
           </label>
         </div>

249-253: Announce status updates more reliably.

Add role and atomic so SRs read concise changes.

-      <div className={styles.statusBar} aria-live="polite">
+      <div className={styles.statusBar} role="status" aria-live="polite" aria-atomic="true">

259-268: Clear error state while typing and hint mobile keyboards.

Reset the red state on edit and help phones show a numeric keypad.

             <input
               type="text"
               value={userAnswer}
-              onChange={(e) => setUserAnswer(e.target.value)}
+              onChange={(e) => {
+                setUserAnswer(e.target.value)
+                if (isWrong) setIsWrong(false)
+              }}
               onKeyDown={(e) => { if (e.key === 'Enter') onSubmitAnswer() }}
               className={`${styles.input} ${isWrong ? styles.wrong : ''}`}
               aria-label="Your answer"
               placeholder="?"
               ref={inputRef}
+              inputMode="numeric"
             />
src/app/game/bullet-math/page.module.css (6)

23-34: Prevent overflow on very narrow screens.

Make the minimum responsive.

-  min-width: 360px;
+  min-width: min(360px, 100%);

36-40: Remove the commented-out .buttons block.

Dead code adds noise; safe to delete.

-/* .buttons {
-  display: flex;
-  gap: 16px;
-  margin-top: 10px;
-} */

51-54: Drop unused .true/.false variants.

They aren’t referenced; trim CSS surface.

-.true { background: #16a34a; color: white; }
-.false { background: #dc2626; color: white; }
 .neutral { background: #334155; color: white; }

84-96: Limit transitioned properties (avoid transition: all).

Reduces layout/repaint work.

-  transition: all 0.3s ease;
+  transition: transform 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;

103-121: Respect prefers-reduced-motion.

Disable hover transforms for motion-sensitive users.

 .menuButton:hover {
   background: linear-gradient(135deg, #2563eb, #1e40af);
   transform: translateY(-2px);
 }
+
+@media (prefers-reduced-motion: reduce) {
+  .menuButton,
+  .resetButton {
+    transition: none;
+  }
+  .menuButton:hover,
+  .resetButton:hover {
+    transform: none;
+  }
+}

143-159: Mobile tweaks look good; keep the responsive min-width consistent.

Mirror the responsive min-width change here too.

   .equationBox {
-    min-width: auto;
+    min-width: auto; /* already responsive above via min(360px, 100%) */
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af4affd and a4c3915.

📒 Files selected for processing (2)
  • src/app/game/bullet-math/page.module.css (1 hunks)
  • src/app/game/bullet-math/page.tsx (1 hunks)
🔇 Additional comments (2)
src/app/game/bullet-math/page.tsx (2)

25-36: Nice exhaustive switch for Op.

This is clean and future-safe. Great use of the never check.


197-206: Good fix: attempts increment only after valid numeric input.

This now matches “attempted questions” semantics and avoids counting empty/invalid submits. Nice.

Comment on lines +52 to +61
if (diff === 'amateur') {
if (op === '÷') {
const { a: aa, b: bb } = makeDivisible(1, 81, 1, 9)
a = aa; b = bb
} else if (op === '×') {
a = randint(1, 9); b = randint(1, 9)
} else {
a = randint(1, 20); b = randint(1, 20)
}
} else if (diff === 'normal') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Match PR spec: Amateur should use single-digit operands for +/-.

Your PR description says “single-digit operations” for Amateur, but +/− currently use 1–20. Suggest 1–9.

-    } else {
-      a = randint(1, 20); b = randint(1, 20)
+    } else {
+      a = randint(1, 9); b = randint(1, 9)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (diff === 'amateur') {
if (op === '÷') {
const { a: aa, b: bb } = makeDivisible(1, 81, 1, 9)
a = aa; b = bb
} else if (op === '×') {
a = randint(1, 9); b = randint(1, 9)
} else {
a = randint(1, 20); b = randint(1, 20)
}
} else if (diff === 'normal') {
if (diff === 'amateur') {
if (op === '÷') {
const { a: aa, b: bb } = makeDivisible(1, 81, 1, 9)
a = aa; b = bb
} else if (op === '×') {
a = randint(1, 9); b = randint(1, 9)
} else {
a = randint(1, 9); b = randint(1, 9)
}
} else if (diff === 'normal') {
🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.tsx around lines 52 to 61, the amateur
difficulty currently generates operands for + and - using randint(1, 20); change
that so amateur uses single-digit operands like the PR spec: replace randint(1,
20) with randint(1, 9) in the else branch (leave the ÷ and × branches as they
are).

Comment on lines +118 to +121
export default function BulletMath() {
type Difficulty = 'amateur' | 'normal' | 'veteran'
const [difficulty, setDifficulty] = useState<Difficulty | undefined>(undefined)
const [running, setRunning] = useState(false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Blocker: difficulty can be undefined and '' is assigned later — breaks types and Start flow.

Today Start can run with no difficulty, and reset sets an invalid value (''). Let’s make difficulty always valid and remove the duplicate type inside the component.

Apply:

-export default function BulletMath() {
-  type Difficulty = 'amateur' | 'normal' | 'veteran'
-  const [difficulty, setDifficulty] = useState<Difficulty | undefined>(undefined)
+export default function BulletMath() {
+  // Difficulty is already declared at module scope; no need to redeclare inside.
+  const [difficulty, setDifficulty] = useState<Difficulty>('amateur')
   const startGame = useCallback(() => {
@@
-    setEquation(generateEquation(difficulty))
+    setEquation(generateEquation(difficulty))
   const getPointsForDifficulty = (diff: Difficulty) => {
   const resetGame = () => {
@@
-    // reset difficulty selection so user can pick again
-    setDifficulty('') 
+    // keep prior selection; radios are re-enabled when not running

Also applies to: 136-145, 186-192, 209-217, 219-229

Comment on lines +329 to +334
<h2>🔬 Scientific Backing</h2>
<p>
Research shows that regular mental arithmetic can improve working memory and
cognitive flexibility. See: <i>“Cognitive Benefits of Mental Calculation” –
Smith et al., 2020</i>.
</p>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Avoid unverifiable citation in UI.

“Smith et al., 2020” isn’t a linked/known reference here. Either link to a real source or rephrase generically.

-        Research shows that regular mental arithmetic can improve working memory and
-        cognitive flexibility. See: <i>“Cognitive Benefits of Mental Calculation” –
-        Smith et al., 2020</i>.
+        Research suggests regular mental arithmetic may support working memory and
+        cognitive flexibility.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<h2>🔬 Scientific Backing</h2>
<p>
Research shows that regular mental arithmetic can improve working memory and
cognitive flexibility. See: <i>“Cognitive Benefits of Mental Calculation”
Smith et al., 2020</i>.
</p>
<h2>🔬 Scientific Backing</h2>
<p>
Research suggests regular mental arithmetic may support working memory and
cognitive flexibility.
</p>
🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.tsx around lines 329 to 334, the UI includes an
unverifiable citation “Smith et al., 2020”; either replace it with a real,
linked source (add a proper URL and update the text to include the full citation
and ensure link opens in a new tab) or rephrase the sentence to a generic claim
without a specific author (e.g., “Research shows” or “Studies suggest”) and
remove the “Smith et al., 2020” fragment so the UI no longer presents an
unreferenced citation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (8)
src/app/game/bullet-math/page.tsx (8)

52-60: Align Amateur spec to single‑digit +/−.

PR says “single-digit operations” for Amateur; +/− currently use 1–20.

Apply:

-    } else {
-      a = randint(1, 20); b = randint(1, 20)
+    } else {
+      a = randint(1, 9); b = randint(1, 9)

118-121: Blocker: difficulty can be undefined; also redeclared type.

generateEquation expects Difficulty, but state is Difficulty | undefined and you redeclare the type locally.

Apply:

-export default function BulletMath() {
-  type Difficulty = 'amateur' | 'normal' | 'veteran'
-  const [difficulty, setDifficulty] = useState<Difficulty | undefined>(undefined)
+export default function BulletMath() {
+  // Use the module-level Difficulty and default to a valid selection
+  const [difficulty, setDifficulty] = useState<Difficulty>('amateur')

Also applies to: 141-145


130-134: Harden timer: remove unsafe casts, use window. and null the ref.*

Prevents type hacks, double-clears, and leaks.

Apply:

-  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
+  const timerRef = useRef<number | null>(null)
@@
-    timerRef.current = setInterval(() => {
+    timerRef.current = window.setInterval(() => {
       setTimeLeft(t => {
         if (t <= 1) {
-          clearInterval(timerRef.current as unknown as number)
+          if (timerRef.current != null) {
+            window.clearInterval(timerRef.current)
+            timerRef.current = null
+          }
           setRunning(false)
           setGameOver(true)
           return 0
         }
         return t - 1
       })
     }, 1000)
     return () => {
-      if (timerRef.current) clearInterval(timerRef.current as unknown as number)
+      if (timerRef.current != null) {
+        window.clearInterval(timerRef.current)
+        timerRef.current = null
+      }
     }

Also applies to: 147-163


186-192: Make points switch exhaustive.

Future Difficulty additions will then fail fast.

Apply:

   const getPointsForDifficulty = (diff: Difficulty) => {
     switch (diff) {
       case 'amateur': return 1
       case 'normal': return 2
       case 'veteran': return 3
+      default: {
+        const _exhaustive: never = diff
+        return _exhaustive
+      }
     }
   }

235-246: Group radios and add radiogroup semantics.

Improves screen reader behavior and keyboard UX.

Apply:

-        <div className={styles.radioGroup}>
+        <div className={styles.radioGroup} role="radiogroup" aria-label="Difficulty">
           <label>
-            <input type="radio" checked={difficulty === 'amateur'} onChange={() => setDifficulty('amateur')} disabled={running}/> Amateur
+            <input name="difficulty" type="radio" checked={difficulty === 'amateur'} onChange={() => setDifficulty('amateur')} disabled={running}/> Amateur
           </label>
           <label>
-            <input type="radio" checked={difficulty === 'normal'} onChange={() => setDifficulty('normal')} disabled={running}/> Normal
+            <input name="difficulty" type="radio" checked={difficulty === 'normal'} onChange={() => setDifficulty('normal')} disabled={running}/> Normal
           </label>
           <label>
-            <input type="radio" checked={difficulty === 'veteran'} onChange={() => setDifficulty('veteran')} disabled={running}/> Veteran
+            <input name="difficulty" type="radio" checked={difficulty === 'veteran'} onChange={() => setDifficulty('veteran')} disabled={running}/> Veteran
           </label>
         </div>

249-253: Polish live region announcements.

Add role and atomic so updates are read succinctly.

Apply:

-      <div className={styles.statusBar} aria-live="polite">
+      <div className={styles.statusBar} role="status" aria-live="polite" aria-atomic="true">

260-268: Clear error on edit and hint mobile keypads.

Resets red state immediately and improves mobile input UX.

Apply:

             <input
               type="text"
               value={userAnswer}
-              onChange={(e) => setUserAnswer(e.target.value)}
+              onChange={(e) => {
+                setUserAnswer(e.target.value)
+                if (isWrong) setIsWrong(false)
+              }}
               onKeyDown={(e) => { if (e.key === 'Enter') onSubmitAnswer() }}
               className={`${styles.input} ${isWrong ? styles.wrong : ''}`}
               aria-label="Your answer"
               placeholder="?"
               ref={inputRef}
+              inputMode="numeric"
+              pattern="-?[0-9]*"
             />

329-334: Avoid unverifiable citation in UI.

Rephrase or link a real source.

Apply:

-        Research shows that regular mental arithmetic can improve working memory and
-        cognitive flexibility. See: <i>“Cognitive Benefits of Mental Calculation” –
-        Smith et al., 2020</i>.
+        Research suggests regular mental arithmetic may support working memory and
+        cognitive flexibility.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4c3915 and 207873e.

📒 Files selected for processing (1)
  • src/app/game/bullet-math/page.tsx (1 hunks)
🔇 Additional comments (2)
src/app/game/bullet-math/page.tsx (2)

25-36: Nice: exhaustive op switch.

Good defensive typing with the never check. Solid.


197-206: Good fix: attempts increment after input validation.

This matches the PR note and avoids counting empty/non‑numeric submits. 👍

Comment on lines +38 to +45
// Ensure division problems are clean
function makeDivisible(aMin: number, aMax: number, bMin: number, bMax: number) {
let b = randint(bMin, bMax)
if (b === 0) b = 1
const k = randint(Math.max(1, Math.ceil(aMin / b)), Math.max(1, Math.floor(aMax / b)))
const a = b * k
return { a, b }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Edge‑case guard in makeDivisible.

If ceil(aMin/b) > floor(aMax/b), randint(min,max) can underflow. Not hit by current ranges, but easy to harden.

Apply:

-  const k = randint(Math.max(1, Math.ceil(aMin / b)), Math.max(1, Math.floor(aMax / b)))
+  const kMin = Math.max(1, Math.ceil(aMin / b))
+  const kMax = Math.max(kMin, Math.floor(aMax / b))
+  const k = randint(kMin, kMax)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Ensure division problems are clean
function makeDivisible(aMin: number, aMax: number, bMin: number, bMax: number) {
let b = randint(bMin, bMax)
if (b === 0) b = 1
const k = randint(Math.max(1, Math.ceil(aMin / b)), Math.max(1, Math.floor(aMax / b)))
const a = b * k
return { a, b }
}
// Ensure division problems are clean
function makeDivisible(aMin: number, aMax: number, bMin: number, bMax: number) {
let b = randint(bMin, bMax)
if (b === 0) b = 1
const kMin = Math.max(1, Math.ceil(aMin / b))
const kMax = Math.max(kMin, Math.floor(aMax / b))
const k = randint(kMin, kMax)
const a = b * k
return { a, b }
}
🤖 Prompt for AI Agents
In src/app/game/bullet-math/page.tsx around lines 38 to 45, the computation of k
uses randint(Math.max(1, Math.ceil(aMin / b)), Math.max(1, Math.floor(aMax /
b))) which can underflow if Math.ceil(aMin/b) > Math.floor(aMax/b); compute minK
= Math.max(1, Math.ceil(aMin / b)) and maxK = Math.floor(aMax / b), then if maxK
< minK set maxK = minK (or clamp maxK to minK) before calling randint(minK,
maxK) so the range is always valid; keep the existing b zero-guard.

Comment on lines +287 to +303
{gameOver && (
<div className={styles.overlay} role="dialog" aria-modal="true" aria-labelledby="times-up-heading">
<div className={styles.completionMessage}>
<h2 id="times-up-heading">🎉 Time’s Up!</h2>
<div className={styles.finalStats}>
<p>⭐ Score: {score}</p>
<p>📊 Questions Attempted: {attempted}</p>
</div>
<button type="button" className={styles.resetButton} onClick={resetGame} ref={resetRef}>
🔄 Play Again
</button>
<button type="button" className={styles.menuButton} onClick={() => router.push('/')}>
🏠 Back to Homepage
</button>
</div>
</div>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Optional: trap focus inside the end‑game dialog.

Currently Tab can escape the modal; add a lightweight focus trap (library or sentinels) while gameOver is true.

Want a minimal, dependency‑free trap snippet?

@ajaynegi33

Copy link
Copy Markdown
Owner
Screenshot 2025-09-07 at 4 01 44 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request gssoc GirlScript Summer Of Code gssoc25 level3 size:L This PR changes 100-499 lines, ignoring generated files size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants