Files
gsd-framework/src/managers/GridManager.ts
T
tiennm99 4f1a0fdb9d feat(02-02): implement Renderer class with tile and selection rendering
- Created Renderer class with tile rendering, selection highlighting, and fade-in animations
- Created GridManager class with tile array and selection state management (blocking dependency)
- Added tilesSelected event to GameEvents interface
- Created comprehensive test suite for Renderer with 9 test cases

**Renderer Features:**
- Renders all non-cleared tiles from GridManager at correct positions
- Centers grid horizontally and vertically within canvas
- Displays emoji characters centered within tile bounds
- Selection highlights with border (3px) and background tint (30% opacity)
- Fade-in animation over ~100ms using performance.now()
- Uses CONFIG.tile.size, gap, cornerRadius for positioning
- Respects CONFIG.colors for styling

**GridManager Features:**
- Manages 2D tile array (10x16 grid = 160 tiles)
- Selection state tracking with toggle behavior (0-2 tiles)
- selectTile() with toggle deselect and cleared tile filtering
- Emits tilesSelected event when 2 tiles selected
- getTileAt() for coordinate-based tile access

**Note:** Tests created but not runnable due to sandbox file system restrictions
preventing npm install. Implementation verified manually against plan requirements.


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 02:51:11 +00:00

119 lines
3.3 KiB
TypeScript

// src/managers/GridManager.ts - Manages 2D tile array and selection state
/**
* GridManager handles the tile grid and selection state.
* It provides methods to access tiles, manage selection, and emit events.
*/
import { Tile } from '../models/Tile';
import { TilePosition, GameEvents } from '../types';
import { TypedEventEmitter } from '../game/EventEmitter';
import { CONFIG } from '../config';
export class GridManager {
private tiles: Tile[][] = [];
private selectedTiles: Tile[] = [];
private events: TypedEventEmitter<GameEvents>;
constructor(events?: TypedEventEmitter<GameEvents>) {
// Allow optional events parameter for testing
this.events = events || new TypedEventEmitter<GameEvents>();
}
/**
* Initialize the grid with tiles based on CONFIG dimensions
*/
initializeGrid(): void {
this.tiles = [];
for (let row = 0; row < CONFIG.grid.rows; row++) {
const rowTiles: Tile[] = [];
for (let col = 0; col < CONFIG.grid.cols; col++) {
const id = `tile-${row}-${col}`;
// Assign types 0-15 repeating to create pairs
const type = (row * CONFIG.grid.cols + col) % 16;
const position: TilePosition = { row, col };
const tile = new Tile(id, type, position);
rowTiles.push(tile);
}
this.tiles.push(rowTiles);
}
}
/**
* Get tile at specific grid coordinates
* @param row - Row index (0-9)
* @param col - Column index (0-15)
* @returns Tile or null if out of bounds
*/
getTileAt(row: number, col: number): Tile | null {
if (row < 0 || row >= CONFIG.grid.rows || col < 0 || col >= CONFIG.grid.cols) {
return null;
}
return this.tiles[row][col];
}
/**
* Select or deselect a tile
* Implements toggle behavior: clicking selected tile deselects it
* Ignores cleared tiles
* Emits tilesSelected event when 2 tiles are selected
* @param tile - Tile to select/deselect
*/
selectTile(tile: Tile): void {
// Ignore cleared tiles
if (tile.cleared) {
return;
}
// Check if tile is already selected
const selectedIndex = this.selectedTiles.findIndex(t => t.id === tile.id);
if (selectedIndex !== -1) {
// Toggle deselect: remove from selection
this.selectedTiles.splice(selectedIndex, 1);
} else if (this.selectedTiles.length < 2) {
// Add to selection if less than 2 selected
this.selectedTiles.push(tile);
// Emit event when 2 tiles selected
if (this.selectedTiles.length === 2) {
this.events.emit('tilesSelected', {
tile1: this.selectedTiles[0],
tile2: this.selectedTiles[1]
});
}
}
// If 2 tiles already selected, ignore (input blocked)
}
/**
* Deselect all tiles
*/
deselectAll(): void {
this.selectedTiles = [];
}
/**
* Get currently selected tiles
* @returns Copy of selected tiles array
*/
get selectedTilesList(): Tile[] {
return [...this.selectedTiles];
}
/**
* Get all tiles in the grid
* @returns 2D array of tiles
*/
getAllTiles(): Tile[][] {
return this.tiles;
}
/**
* Get the event emitter for external subscription
*/
getEvents(): TypedEventEmitter<GameEvents> {
return this.events;
}
}