-
Notifications
You must be signed in to change notification settings - Fork 0
Implement minimal Math Marauders arcade loop prototype #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Summary of ChangesHello @cs-util, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request establishes the foundational arcade loop for the Math Marauders game. It transitions the project from a placeholder to a functional prototype, integrating core gameplay mechanics like math gate interactions, combat encounters, and a reverse chase sequence. The changes introduce a modular architecture with dedicated systems for game logic, UI, and performance management, all supported by a robust testing suite to ensure determinism and responsiveness. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This is an excellent pull request that successfully implements the core arcade loop for Math Marauders. The code is well-structured, modular, and accompanied by a comprehensive suite of unit and integration tests, which is fantastic for a prototype. My feedback focuses on a few areas to enhance clarity, consistency, and maintainability, primarily by addressing magic numbers and clarifying UI element actions.
<h2>Paused</h2> | ||
<button data-action="resume">Resume</button> | ||
<button data-action="restart">Restart</button> | ||
<button data-action="mute">Toggle Bloom</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This button's text is "Toggle Bloom", but its data-action
is "mute". The corresponding JavaScript in src/index.js
calls toggleMuteOverlay()
, which toggles an is-muted
class on the pause overlay. There are three issues here:
- The button text doesn't match its
data-action
. - The function name
toggleMuteOverlay
suggests muting, not a bloom effect. - The
is-muted
class is not used in the CSS, so the button has no visible effect.
Please align the button's text, action, and implementation. If it's for toggling bloom, the action should be data-action="toggle-bloom"
and the JS should implement that visual effect.
<button data-action="mute">Toggle Bloom</button> | |
<button data-action="toggle-bloom">Toggle Bloom</button> |
export function simulateSkirmish({ | ||
playerCount, | ||
enemyCount, | ||
volleyDurationMs, | ||
}) { | ||
const volleys = Math.max(1, Math.round(volleyDurationMs / 1000)); | ||
const playerDamagePerVolley = Math.max(1, Math.round(enemyCount * 0.1)); | ||
const enemyDamagePerVolley = Math.max(1, Math.round(playerCount * 0.12)); | ||
|
||
const playerLoss = Math.min(playerCount, playerDamagePerVolley * volleys); | ||
const enemyLoss = Math.min(enemyCount, enemyDamagePerVolley * volleys); | ||
|
||
const playerRemaining = Math.max(0, playerCount - playerLoss); | ||
const enemyRemaining = Math.max(0, enemyCount - enemyLoss); | ||
const scoreDelta = enemyLoss * 2 - playerLoss; | ||
|
||
return { | ||
volleys, | ||
playerRemaining, | ||
enemyRemaining, | ||
scoreDelta, | ||
}; | ||
} | ||
|
||
export function resolveReverseChase({ survivors, chaseStrength }) { | ||
const pressureThreshold = Math.ceil(chaseStrength * 0.7); | ||
if (survivors <= pressureThreshold) { | ||
return { | ||
success: false, | ||
casualties: survivors, | ||
bonusScore: 0, | ||
}; | ||
} | ||
|
||
const casualties = Math.max(0, Math.round(chaseStrength * 0.35)); | ||
const bonusScore = Math.max(0, survivors - casualties); | ||
|
||
return { | ||
success: true, | ||
casualties, | ||
bonusScore, | ||
}; | ||
} | ||
|
||
export function calculateStarRating(score) { | ||
if (score >= 110) return 3; | ||
if (score >= 60) return 2; | ||
return 1; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file uses several "magic numbers" for game balance calculations (e.g., damage multipliers, chase thresholds, score values, star rating bands). Extracting these into a configuration object or named constants at the top of the file would make the logic much clearer and easier for designers to tune.
const COMBAT_CONFIG = {
PLAYER_DAMAGE_MULTIPLIER: 0.12,
ENEMY_DAMAGE_MULTIPLIER: 0.1,
SCORE_MULTIPLIER_ENEMY_LOSS: 2,
CHASE_PRESSURE_THRESHOLD_MULTIPLIER: 0.7,
CHASE_CASUALTIES_MULTIPLIER: 0.35,
STAR_RATING_3: 110,
STAR_RATING_2: 60,
};
export function simulateSkirmish({
playerCount,
enemyCount,
volleyDurationMs,
}) {
const volleys = Math.max(1, Math.round(volleyDurationMs / 1000));
const playerDamagePerVolley = Math.max(
1,
Math.round(enemyCount * COMBAT_CONFIG.ENEMY_DAMAGE_MULTIPLIER)
);
const enemyDamagePerVolley = Math.max(
1,
Math.round(playerCount * COMBAT_CONFIG.PLAYER_DAMAGE_MULTIPLIER)
);
const playerLoss = Math.min(playerCount, playerDamagePerVolley * volleys);
const enemyLoss = Math.min(enemyCount, enemyDamagePerVolley * volleys);
const playerRemaining = Math.max(0, playerCount - playerLoss);
const enemyRemaining = Math.max(0, enemyCount - enemyLoss);
const scoreDelta =
enemyLoss * COMBAT_CONFIG.SCORE_MULTIPLIER_ENEMY_LOSS - playerLoss;
return {
volleys,
playerRemaining,
enemyRemaining,
scoreDelta,
};
}
export function resolveReverseChase({ survivors, chaseStrength }) {
const pressureThreshold = Math.ceil(
chaseStrength * COMBAT_CONFIG.CHASE_PRESSURE_THRESHOLD_MULTIPLIER
);
if (survivors <= pressureThreshold) {
return {
success: false,
casualties: survivors,
bonusScore: 0,
};
}
const casualties = Math.max(
0,
Math.round(chaseStrength * COMBAT_CONFIG.CHASE_CASUALTIES_MULTIPLIER)
);
const bonusScore = Math.max(0, survivors - casualties);
return {
success: true,
casualties,
bonusScore,
};
}
export function calculateStarRating(score) {
if (score >= COMBAT_CONFIG.STAR_RATING_3) return 3;
if (score >= COMBAT_CONFIG.STAR_RATING_2) return 2;
return 1;
}
const count = 3; | ||
const selected = ops.slice(0, count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The number of gate options is hardcoded as 3
. This should be a named constant (e.g., GATE_OPTION_COUNT
) defined at the top of the module for better readability and easier configuration.
const count = 3; | |
const selected = ops.slice(0, count); | |
const GATE_OPTION_COUNT = 3; | |
const selected = ops.slice(0, GATE_OPTION_COUNT); |
playerCount: 12, | ||
enemyCount: 10, | ||
chaseStrength: 8, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initial values for playerCount
, enemyCount
, and chaseStrength
are hardcoded. These are important for game balance and should be defined as named constants at the top of the file, similar to RUN_DURATION_MS
. This makes them easier to find and tune.
playerCount: 12, | |
enemyCount: 10, | |
chaseStrength: 8, | |
playerCount: 12, // TODO: Extract to INITIAL_PLAYER_COUNT constant | |
enemyCount: 10, // TODO: Extract to INITIAL_ENEMY_COUNT constant | |
chaseStrength: 8, // TODO: Extract to INITIAL_CHASE_STRENGTH constant |
let nextSeed = nextSeedFromUrl ?? createSeed(); | ||
let state = INITIAL_STATE(nextSeed); | ||
let timerInterval = null; | ||
let pendingTimeouts = []; | ||
let pausedCallbacks = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The application state (state
, nextSeed
, timerInterval
, etc.) and logic are managed as module-level global variables. For a prototype, this is acceptable. However, as the application grows, encapsulating this state and related logic into a class (e.g., a Game
or App
class) would improve structure, make state management more explicit, and enhance maintainability. This is a suggestion for future refactoring.
Summary
Testing
npm test
https://chatgpt.com/codex/tasks/task_e_68ed71ef0c6c832c92b87b4cb54d06e1