Spaces:
Runtime error
Runtime error
| import { Container, Graphics, Text, TextStyle } from 'pixi.js' | |
| import { CELL_SIZE } from 'shared/board.ts' | |
| import { theme, getThemeBackgroundImage, getThemeMode } from '../theme.ts' | |
| const GRID_WIDTH = 1 | |
| const GRID_MAJOR_WIDTH = 2 | |
| const BORDER_WIDTH = 2 | |
| const BOARD_PAD = 1.2 | |
| const p = (gx: number, gy: number) => ({ | |
| x: gx * CELL_SIZE, | |
| y: gy * CELL_SIZE, | |
| }) | |
| export class GridRenderer { | |
| container: Container | |
| private graphics: Graphics | |
| private coordsContainer: Container | |
| private coordLabels: Text[] = [] | |
| private lastBoardSize = 0 | |
| constructor() { | |
| this.graphics = new Graphics() | |
| this.coordsContainer = new Container() | |
| this.coordsContainer.eventMode = 'none' | |
| this.container = new Container() | |
| this.container.addChild(this.graphics) | |
| this.container.addChild(this.coordsContainer) | |
| } | |
| private ensureCoords(boardSize: number): void { | |
| if (boardSize === this.lastBoardSize && this.coordLabels.length > 0) return | |
| this.lastBoardSize = boardSize | |
| this.coordsContainer.removeChildren() | |
| this.coordLabels.length = 0 | |
| const style = new TextStyle({ fontFamily: 'monospace', fontSize: 9, fill: 0xc0b8e8 }) | |
| const cols = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | |
| for (let c = 0; c < boardSize; c++) { | |
| const cx = (c + 0.5) * CELL_SIZE | |
| const tT = new Text({ text: cols[c], style }); tT.alpha = 0.15; tT.anchor.set(0.5, 1); tT.position.set(cx, -5); this.coordsContainer.addChild(tT); this.coordLabels.push(tT) | |
| const tB = new Text({ text: cols[c], style }); tB.alpha = 0.15; tB.anchor.set(0.5, 0); tB.position.set(cx, boardSize * CELL_SIZE + 5); this.coordsContainer.addChild(tB); this.coordLabels.push(tB) | |
| } | |
| for (let r = 0; r < boardSize; r++) { | |
| const cy = (r + 0.5) * CELL_SIZE | |
| const tL = new Text({ text: String(r + 1), style }); tL.alpha = 0.15; tL.anchor.set(1, 0.5); tL.position.set(-7, cy); this.coordsContainer.addChild(tL); this.coordLabels.push(tL) | |
| const tR = new Text({ text: String(r + 1), style }); tR.alpha = 0.15; tR.anchor.set(0, 0.5); tR.position.set(boardSize * CELL_SIZE + 7, cy); this.coordsContainer.addChild(tR); this.coordLabels.push(tR) | |
| } | |
| } | |
| draw(boardSize: number = 15): void { | |
| this.graphics.clear() | |
| const isGarden = getThemeMode() === 'garden' | |
| const hasThemeBackgroundImage = getThemeBackgroundImage() !== null | |
| const cellFillAlpha = hasThemeBackgroundImage ? 0.58 : 0.95 | |
| const gridColor = theme.gridMinor | |
| const gridMajorColor = theme.gridMajor | |
| const borderColor = theme.gridBorder | |
| const start = 0 | |
| const end = boardSize | |
| // Board shadow | |
| const pad = BOARD_PAD | |
| const sTL = p(start - pad, start - pad) | |
| const sBR = p(end + pad, end + pad) | |
| const bw = sBR.x - sTL.x | |
| const bh = sBR.y - sTL.y | |
| for (let i = 0; i < 6; i += 2) { | |
| this.graphics.rect(sTL.x + i, sTL.y + i, bw, bh) | |
| this.graphics.fill({ color: 0x000000, alpha: 0.03 + i * 0.005 }) | |
| } | |
| // Cell fills (alternating board pattern) | |
| for (let r = start; r < end; r++) { | |
| for (let c = start; c < end; c++) { | |
| const idx = r * boardSize + c | |
| const isOdd = idx % 2 === 1 | |
| const fill = isOdd ? theme.cellFillAlt : theme.cellFill | |
| const a = p(c, r) | |
| const b = p(c + 1, r + 1) | |
| this.graphics.rect(a.x, a.y, b.x - a.x, b.y - a.y) | |
| this.graphics.fill({ color: fill, alpha: cellFillAlpha }) | |
| } | |
| } | |
| // Floor cast shadow (garden theme — stacked ellipses below board) | |
| if (isGarden) { | |
| const boardBot = boardSize * CELL_SIZE | |
| const boardCx = (boardSize / 2) * CELL_SIZE | |
| const boardHalfW = (boardSize / 2) * CELL_SIZE | |
| for (let i = 0; i < 6; i++) { | |
| const t = i / 5 | |
| const rx = boardHalfW * (0.7 + t * 0.45) | |
| const ry = 2 + t * 5 | |
| const alpha = 0.28 * (1 - t * 0.9) | |
| const yOff = 2 + t * 7 | |
| this.graphics.ellipse(boardCx, boardBot + yOff, rx, ry) | |
| this.graphics.fill({ color: 0x000000, alpha }) | |
| } | |
| } | |
| // Board base (3D edge — visible thickness at bottom) | |
| const edgeH = 10 | |
| this.graphics.rect(sTL.x, sTL.y + edgeH, bw, bh) | |
| this.graphics.fill({ color: 0x000000, alpha: 0.12 }) | |
| this.graphics.rect(sTL.x, sTL.y, bw, bh) | |
| this.graphics.fill({ color: 0x000000, alpha: 0.06 }) | |
| // Grid lines — vertical | |
| for (let col = start; col <= end; col++) { | |
| const isMajor = col % 5 === 0 | |
| const a = p(col, start) | |
| const b = p(col, end) | |
| this.graphics.moveTo(a.x, a.y) | |
| this.graphics.lineTo(b.x, b.y) | |
| this.graphics.stroke({ | |
| width: isMajor ? GRID_MAJOR_WIDTH : GRID_WIDTH, | |
| color: isMajor ? gridMajorColor : gridColor, | |
| alpha: isMajor ? 1 : 0.6, | |
| }) | |
| } | |
| // Grid lines — horizontal | |
| for (let row = start; row <= end; row++) { | |
| const isMajor = row % 5 === 0 | |
| const a = p(start, row) | |
| const b = p(end, row) | |
| this.graphics.moveTo(a.x, a.y) | |
| this.graphics.lineTo(b.x, b.y) | |
| this.graphics.stroke({ | |
| width: isMajor ? GRID_MAJOR_WIDTH : GRID_WIDTH, | |
| color: isMajor ? gridMajorColor : gridColor, | |
| alpha: isMajor ? 1 : 0.6, | |
| }) | |
| } | |
| // Border | |
| const border = p(start, start) | |
| this.graphics.rect(border.x, border.y, (end - start) * CELL_SIZE, (end - start) * CELL_SIZE) | |
| this.graphics.stroke({ width: BORDER_WIDTH, color: borderColor }) | |
| this.ensureCoords(boardSize) | |
| this.coordsContainer.visible = isGarden | |
| } | |
| } | |