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 } }