Conversation
WalkthroughThis PR refactors a Battleship game from procedural code into a modular, class-based architecture. Core game logic, ship tracking, board management, and AI behavior are extracted into separate modules. CSS is redesigned from legacy styles to a token-driven system with new animations. The entry point now bridges UI events to game methods. Changes
Sequence DiagramsequenceDiagram
participant User
participant UI as UI Events<br/>(DOM)
participant Bridge as battleship.js<br/>(Bridge)
participant Game as BattleshipGame
participant Board as Board<br/>(Player/CPU)
participant AI as AIController
participant UICtrl as UIController
User->>UI: Click board cell
UI->>Bridge: Trigger event handler
Bridge->>Game: playerAttack(index)
Game->>Board: CPU board receives attack
Board->>Game: Hit/miss result
Game->>UICtrl: renderHit/renderMiss(result)
UICtrl->>UI: Update board visuals
alt CPU turn
Game->>AI: nextAttack()
AI->>Game: index
Game->>Board: Player board receives attack
Board->>Game: Hit/miss result
Game->>AI: handleAttackResult()
Game->>UICtrl: Render CPU attack
end
alt Victory or Defeat
Game->>UICtrl: showVictory()/showDefeat()
UICtrl->>UI: Display outcome with retry
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes The refactor introduces six new modular classes with game logic, AI behavior, and UI coordination. While each module follows a clear pattern, verifying interactions between Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (8)
AGENTS.md (2)
4-4: Improve readability by breaking up the long sentence.The project structure paragraph is a single 150+ word sentence that's difficult to parse. Consider breaking it into multiple sentences or a bulleted list for better readability.
Apply this diff to improve clarity:
-`index.html` is the single-page shell; it links the modern styling in `battleship.css` and boots `battleship.js` as an ES module. Gameplay logic lives under `src/`: `game.js` orchestrates turns and win states, `board.js` tracks ship placement and attacks, `ship.js` models individual vessels, `ai-controller.js` handles smart CPU targeting, and `ui-controller.js` updates the DOM. Shared constants (ship specs, orientations, board size) are defined in `src/constants.js` so both players stay in sync. `battleship.js` wires the controllers together, exposes the global bridge required by the existing `onclick` handlers, and inserts the current year into the footer. Static assets (splash/explosion SVGs, icons, screenshots) live in `images/`; prefer `.webp` for screenshots and keep SVGs optimized (`svgo` or similar) when adding new art. +`index.html` is the single-page shell; it links the modern styling in `battleship.css` and boots `battleship.js` as an ES module. + +Gameplay logic lives under `src/`: +- `game.js` orchestrates turns and win states +- `board.js` tracks ship placement and attacks +- `ship.js` models individual vessels +- `ai-controller.js` handles smart CPU targeting +- `ui-controller.js` updates the DOM +- `constants.js` defines shared constants (ship specs, orientations, board size) + +`battleship.js` wires the controllers together, exposes the global bridge required by the existing `onclick` handlers, and inserts the current year into the footer. Static assets (splash/explosion SVGs, icons, screenshots) live in `images/`; prefer `.webp` for screenshots and keep SVGs optimized (`svgo` or similar) when adding new art.
15-15: Consider adding specific test cases to the testing guidelines.While the testing guidelines mention general flows to test, consider adding a checklist of specific test cases (e.g., "Verify placing a 5-cell battleship vertically at position 95 shows red preview", "Confirm CPU doesn't attack the same cell twice").
src/game.js (1)
244-265: CPU fleet deployment could theoretically loop indefinitely.The
while (!placed)loop on Line 249 attempts random placement until successful. On a nearly-full board or with an impossible configuration, this could loop many times. In practice, with a 10×10 board and only 5 ships (18 cells), this is extremely unlikely. Consider adding a maximum attempt counter if you want extra safety.src/ai-controller.js (1)
12-20: Consider using a more efficient queue implementation.The
focusQueue.shift()operation is O(n) for arrays. For small queues (typically <10 items in this game), this is acceptable. If you want to optimize, consider using a dedicated queue data structure or tracking a head index.battleship.css (1)
284-292: Table sizing is tightly coupled to grid dimensions.Line 286-287 hardcode the calculation
calc(var(--cell-size) * 10 + 9 * 5px), assuming a 10×10 grid with 5px spacing. This works for the current BOARD_SIZE constant but would break if the board size changes. Consider using a CSS custom property for board size if you anticipate this changing.src/board.js (2)
78-101: Refactor to eliminate code duplication.The cell generation logic in
previewPlacement(lines 82-90) duplicates the logic increateShipCells(lines 40-46). This violates the DRY principle.Consider refactoring to reuse
createShipCells:previewPlacement(length, startIndex, orientation) { const { row, col } = this.indexToCoord(startIndex); - const cells = []; - - for (let i = 0; i < length; i++) { - const targetRow = - orientation === ORIENTATION.VERTICAL ? row + i : row; - const targetCol = - orientation === ORIENTATION.HORIZONTAL ? col + i : col; - if (this.isWithinGrid(targetRow, targetCol)) { - cells.push(this.coordToIndex(targetRow, targetCol)); - } - } + const allCells = this.createShipCells(length, row, col, orientation); + const cells = allCells.filter(index => { + const coord = this.indexToCoord(index); + return this.isWithinGrid(coord.row, coord.col); + }); const inBounds = this.isInBounds(length, row, col, orientation); const overlaps = cells.some((cell) => this.occupiedCells.has(cell)); return { cells, inBounds, overlaps, isValid: inBounds && !overlaps && cells.length === length, }; }
123-137: Consider caching available cells for better performance.The current implementation scans all cells on every call (O(n) where n=100 for a 10×10 board). While acceptable for this board size, maintaining a Set of available cells updated during
receiveAttackwould reduce this to O(1) lookup + O(1) random selection.If you'd like to optimize:
reset() { this.ships = []; this.shipLookup = new Map(); this.occupiedCells = new Set(); this.attackedCells = new Set(); + const totalCells = this.size * this.size; + this.availableCells = new Set(Array.from({length: totalCells}, (_, i) => i)); } receiveAttack(index) { if (this.hasBeenAttacked(index)) { return { alreadyAttacked: true }; } this.attackedCells.add(index); + this.availableCells.delete(index); const ship = this.shipLookup.get(index); if (ship) { ship.recordHit(index); return { hit: true, shipSunk: ship.isSunk(), ship }; } return { hit: false }; } randomUnattackedIndex() { - const available = []; - const totalCells = this.size * this.size; - for (let index = 0; index < totalCells; index++) { - if (!this.attackedCells.has(index)) { - available.push(index); - } - } - if (!available.length) { + if (!this.availableCells.size) { return null; } - const choice = - available[Math.floor(Math.random() * available.length)]; - return choice; + const available = Array.from(this.availableCells); + return available[Math.floor(Math.random() * available.length)]; }src/ui-controller.js (1)
104-110: Consider validating className to prevent potential XSS.While current usage passes only hardcoded strings,
renderSquareaccepts arbitraryclassNameparameters and directly interpolates them intoinnerHTML. If future code passes user-controlled data, this becomes an XSS vector.Add a whitelist check or use
createElement:renderSquare(board, index, className) { const el = this.getSquareElement(board, index); if (!el) { return; } - el.innerHTML = `<div class='${className}'></div>`; + const validClasses = new Set(['ship-select', 'ship-hit', 'ship-miss', 'preview-valid', 'preview-invalid']); + if (!validClasses.has(className)) { + console.warn(`Invalid className: ${className}`); + return; + } + const div = this.doc.createElement('div'); + div.className = className; + el.innerHTML = ''; + el.appendChild(div); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
AGENTS.md(1 hunks)battleship.css(1 hunks)battleship.js(1 hunks)index.html(3 hunks)src/ai-controller.js(1 hunks)src/board.js(1 hunks)src/constants.js(1 hunks)src/game.js(1 hunks)src/ship.js(1 hunks)src/ui-controller.js(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/game.js (3)
src/constants.js (6)
SHIP_DEFINITIONS(3-8)SHIP_DEFINITIONS(3-8)ORIENTATION(15-18)ORIENTATION(15-18)TOTAL_SHIP_CELLS(10-13)TOTAL_SHIP_CELLS(10-13)src/board.js (1)
Board(4-142)src/ai-controller.js (1)
AIController(1-59)
battleship.js (3)
src/ui-controller.js (1)
UIController(1-117)src/game.js (1)
BattleshipGame(14-271)src/constants.js (2)
ORIENTATION(15-18)ORIENTATION(15-18)
src/board.js (2)
src/constants.js (4)
BOARD_SIZE(1-1)BOARD_SIZE(1-1)ORIENTATION(15-18)ORIENTATION(15-18)src/ship.js (1)
Ship(3-24)
🔇 Additional comments (24)
battleship.js (2)
14-37: LGTM! Clean bridge pattern implementation.The bridge pattern effectively exposes game methods to the global scope for the existing inline onclick handlers while keeping the game logic modular. The Number() conversions ensure type safety when converting string IDs to numbers.
41-56: Preview event handlers are well-implemented.The mouseenter/mouseleave event listeners provide good UX by showing placement previews. The table-level mouseleave handler ensures previews are cleared when the cursor leaves the board entirely.
index.html (3)
10-13: Font loading strategy is well-implemented.The preconnect hints optimize font loading performance, and the stylesheet uses
display=swapto prevent invisible text during font loading.
341-341: Dynamic year rendering eliminates manual updates.Good decision to make the copyright year dynamic, preventing the need for annual updates.
350-350: Module script loading is correct.The
type="module"attribute properly loads the JavaScript as an ES module, enabling the import/export syntax used throughout the codebase.src/constants.js (1)
1-18: Constants are well-defined and correctly calculated.The constants provide a single source of truth for game configuration. TOTAL_SHIP_CELLS correctly sums to 18 (5 + 8 + 3 + 2), and the ORIENTATION object provides clear enum-like values.
src/ship.js (2)
1-9: Ship ID counter never resets across game instances.The module-level
shipIdCounterincrements indefinitely and never resets when starting a new game. This means ship IDs will be "ship-1", "ship-2", etc. in the first game, then "ship-6", "ship-7", etc. in the second game. This is functionally fine since IDs are only for internal tracking, but it's worth noting for future debugging.If this behavior is unintended, consider resetting the counter when starting a new game, or accept it as-is for simplicity.
11-23: Ship methods are correctly implemented.The
occupies(),recordHit(), andisSunk()methods use Sets efficiently and implement the correct logic for tracking ship state.src/game.js (3)
198-203: Win condition uses redundant checks.Both
this.playerHits >= this.totalShipCellsandthis.cpuBoard.allShipsSunk()check for the same condition (all enemy ships destroyed). The hit counter check is faster but could theoretically drift out of sync if there's a bug. Consider using onlyallShipsSunk()for accuracy, or keep both as defensive programming.This pattern is repeated on Lines 237-241 for the CPU win condition. If both checks are intentional for defense-in-depth, that's fine. Otherwise, consider simplifying to use only the ship-based check.
218-226: Good defensive programming with AI fallback.The while loop correctly handles the edge case where the AI might return an already-attacked cell, falling back to random selection. This prevents infinite loops and ensures the game continues smoothly.
152-154: Auto-deselection improves UX.Automatically clearing the selected ship when all instances are placed is good UX, preventing users from attempting to place non-existent ships.
src/ai-controller.js (2)
22-33: AI targeting logic is well-implemented.The hunt-and-kill strategy correctly switches to focused targeting after a hit and resets to random mode after sinking a ship. The check for
result.alreadyAttackedprevents errors from stale queue entries.
35-58: Neighbor enqueueing correctly handles boundaries.The method properly validates grid boundaries with
isWithinGrid()and usesfocusRegistryto prevent duplicate entries in the queue.battleship.css (4)
1-15: Excellent token-driven design system.The CSS variables provide a maintainable, consistent design system. The color tokens, sizing variables, and typography settings create a cohesive visual language.
585-645: Responsive breakpoints are well-designed.The media queries at 960px, 768px, 700px, and 520px provide smooth responsive behavior by adjusting cell size, layout, and spacing appropriately for different viewport sizes.
552-583: Smooth animations enhance UX.The keyframe animations (blast, ripple, pulse) add polish to hit/miss feedback and victory announcements without being overwhelming.
443-443: SVG assets verified—no issues found.Both
images/explosion.svgandimages/splash-1.svgexist in the repository. The CSS references are valid and will not result in broken visuals.src/board.js (3)
4-15: LGTM! Clean initialization pattern.The constructor and reset method properly initialize all state using appropriate data structures (Set for cell tracking, Map for ship lookup).
17-29: LGTM! Correct coordinate conversion logic.The index-to-coordinate and coordinate-to-index conversions are mathematically sound, and bounds checking properly validates all four boundaries.
103-121: LGTM! Attack logic is correct.The attack handling properly guards against duplicate attacks, updates ship state on hits, and returns appropriate status information.
src/ui-controller.js (4)
1-27: LGTM! Good testability pattern.The constructor accepts an optional
docparameter, enabling dependency injection for testing. Methods properly guard against missing DOM elements.
29-38: LGTM! Proper cleanup with null guard.The method correctly removes the selection wrapper from the DOM and nullifies the reference, with proper null-checking to prevent errors on subsequent calls.
60-88: LGTM! Robust preview state management.The preview methods correctly track state in
previewedCells, guard against missing DOM elements, and handle edge cases like empty cell arrays.
112-116: LGTM! Board-specific ID logic is correct.The method correctly handles different ID formats for CPU vs. player boards.
| alert("Select your ship location's first"); | ||
| } | ||
| }; | ||
| //main batttleship game file |
There was a problem hiding this comment.
Fix the typo in the comment.
The comment has "batttleship" with three t's instead of "battleship".
Apply this diff:
-//main batttleship game file
+//main battleship game file📝 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.
| //main batttleship game file | |
| //main battleship game file |
🤖 Prompt for AI Agents
In battleship.js around line 1, the file header comment contains a typo
"batttleship" (three t's); change the comment to read "main battleship game
file" (with two t's) so the word is spelled correctly.
| showVictory() { | ||
| if (this.announcementEl) { | ||
| this.announcementEl.innerHTML = | ||
| "<span class='announcement'><br/>YOU WIN!</span><span onclick='location.reload()' class='retry'>RETRY</span>"; | ||
| } | ||
| } | ||
|
|
||
| showDefeat() { | ||
| if (this.announcementEl) { | ||
| this.announcementEl.innerHTML = | ||
| "<span class='announcement'><br/>YOU LOSE!</span><span onclick='location.reload()' class='retry'>RETRY</span>"; | ||
| } | ||
| } |
There was a problem hiding this comment.
Replace inline event handlers with addEventListener.
The inline onclick handlers in showVictory and showDefeat violate Content Security Policy best practices and modern DOM patterns. This approach won't work with strict CSP headers.
Refactor to use event delegation or explicit listeners:
showVictory() {
if (this.announcementEl) {
this.announcementEl.innerHTML =
- "<span class='announcement'><br/>YOU WIN!</span><span onclick='location.reload()' class='retry'>RETRY</span>";
+ "<span class='announcement'><br/>YOU WIN!</span><span class='retry'>RETRY</span>";
+ const retryBtn = this.announcementEl.querySelector('.retry');
+ if (retryBtn) {
+ retryBtn.addEventListener('click', () => location.reload());
+ }
}
}
showDefeat() {
if (this.announcementEl) {
this.announcementEl.innerHTML =
- "<span class='announcement'><br/>YOU LOSE!</span><span onclick='location.reload()' class='retry'>RETRY</span>";
+ "<span class='announcement'><br/>YOU LOSE!</span><span class='retry'>RETRY</span>";
+ const retryBtn = this.announcementEl.querySelector('.retry');
+ if (retryBtn) {
+ retryBtn.addEventListener('click', () => location.reload());
+ }
}
}📝 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.
| showVictory() { | |
| if (this.announcementEl) { | |
| this.announcementEl.innerHTML = | |
| "<span class='announcement'><br/>YOU WIN!</span><span onclick='location.reload()' class='retry'>RETRY</span>"; | |
| } | |
| } | |
| showDefeat() { | |
| if (this.announcementEl) { | |
| this.announcementEl.innerHTML = | |
| "<span class='announcement'><br/>YOU LOSE!</span><span onclick='location.reload()' class='retry'>RETRY</span>"; | |
| } | |
| } | |
| showVictory() { | |
| if (this.announcementEl) { | |
| this.announcementEl.innerHTML = | |
| "<span class='announcement'><br/>YOU WIN!</span><span class='retry'>RETRY</span>"; | |
| const retryBtn = this.announcementEl.querySelector('.retry'); | |
| if (retryBtn) { | |
| retryBtn.addEventListener('click', () => location.reload()); | |
| } | |
| } | |
| } | |
| showDefeat() { | |
| if (this.announcementEl) { | |
| this.announcementEl.innerHTML = | |
| "<span class='announcement'><br/>YOU LOSE!</span><span class='retry'>RETRY</span>"; | |
| const retryBtn = this.announcementEl.querySelector('.retry'); | |
| if (retryBtn) { | |
| retryBtn.addEventListener('click', () => location.reload()); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/ui-controller.js around lines 40 to 52, the showVictory and showDefeat
methods inject HTML with inline onclick handlers (violates CSP); remove the
inline onclick attributes, render the announcement and retry elements with class
or id, then query the newly added retry element after setting innerHTML and
attach a click listener via addEventListener that calls location.reload();
ensure you check announcementEl exists and the querySelector for the retry
element returns a node before adding the listener so it won't throw.
PR Type
Enhancement
Description
Refactored monolithic 776-line battleship.js into modular ES6 classes
game.js, board management intoboard.jsship.js,ai-controller.js,ui-controller.jsfor separation of concernsconstants.jsfor maintainabilityModernized styling with CSS variables and glassmorphism design
Enhanced AI targeting with smart attack queue system
Improved user experience with placement preview and real-time feedback
Diagram Walkthrough
File Walkthrough
1 files
Replaced monolithic code with modular ES6 entry point6 files
Core game orchestration and turn management logicBoard state, ship placement, and attack trackingShip model with hit tracking and sink detectionSmart CPU targeting with focus queue systemDOM manipulation and visual feedback controllerComplete visual redesign with modern neon theme2 files
Centralized game configuration and ship definitionsUpdated script type to module and added font imports1 files
Added comprehensive project guidelines and documentationSummary by CodeRabbit
Release Notes
New Features
Style
Documentation