import { BoardElement, Color, Size, CoreRequest } from "core-types"; import { assertNever } from "../assertNever"; const MARGIN = 20; const BOARD_WIDTH = 800; const BOARD_HEIGHT = 800; type Pixel = { x: number; y: number }; type Coordinate = { column: number; row: number }; export interface GoBoardProps { board: BoardElement; onClick: (_: CoreRequest) => void; } export class GoBoard { private board: BoardElement; private pen: Pen; private cursorLocation: Coordinate | null; canvas: HTMLCanvasElement; constructor({ board, onClick }: GoBoardProps) { this.board = board; this.canvas = document.createElement("canvas"); this.canvas.width = BOARD_WIDTH; this.canvas.height = BOARD_HEIGHT; this.pen = new Pen( this.canvas.width, this.canvas.height, MARGIN, this.board.size.width, this.board.size.height ); this.cursorLocation = null; this.canvas.onmousemove = (event) => { const bounds = this.canvas.getBoundingClientRect(); const coordinate = { x: event.clientX - bounds.x, y: event.clientY - bounds.y, }; let address = this.pen.address(coordinate); if (this.cursorLocation != address) { this.cursorLocation = this.pen.address(coordinate); this.renderBoard(); } }; this.canvas.onclick = (_) => { console.log("clicked on ", this.cursorLocation); if (this.cursorLocation) { const intersection = this.board.spaces[boardAddress(this.board.size, this.cursorLocation)]; switch (intersection.type) { case "Unplayable": break; case "Empty": console.log("need to run action: ", intersection.content); onClick(intersection.content); break; case "Filled": break; default: assertNever(intersection); } } }; } setBoard(board: BoardElement) { console.log("setting an updated board: ", board); this.board = board; this.pen = new Pen( this.canvas.width, this.canvas.height, MARGIN, this.board.size.width, this.board.size.height ); } renderBoard() { const ctx = this.canvas.getContext("2d"); if (!ctx) { alert("could not get the canvas context"); return null; } ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); ctx.lineWidth = 2; ctx.strokeStyle = "black"; ctx.beginPath(); for (var col = 0; col < this.board.size.width; col++) { ctx.moveTo(MARGIN + col * this.pen.hspaceBetween, MARGIN); ctx.lineTo( MARGIN + col * this.pen.hspaceBetween, MARGIN + (this.board.size.height - 1) * this.pen.vspaceBetween ); } for (var row = 0; row < this.board.size.height; row++) { ctx.moveTo(MARGIN, MARGIN + row * this.pen.vspaceBetween); ctx.lineTo( MARGIN + (this.board.size.width - 1) * this.pen.hspaceBetween, MARGIN + row * this.pen.vspaceBetween ); } ctx.closePath(); ctx.stroke(); this.pen.starPoint(ctx, { column: 3, row: 3 }); this.pen.starPoint(ctx, { column: 3, row: 9 }); this.pen.starPoint(ctx, { column: 3, row: 15 }); this.pen.starPoint(ctx, { column: 9, row: 3 }); this.pen.starPoint(ctx, { column: 9, row: 9 }); this.pen.starPoint(ctx, { column: 9, row: 15 }); this.pen.starPoint(ctx, { column: 15, row: 3 }); this.pen.starPoint(ctx, { column: 15, row: 9 }); this.pen.starPoint(ctx, { column: 15, row: 15 }); if (this.cursorLocation) { this.pen.ghostStone(ctx, this.cursorLocation, Color.White); } } } class Pen { margin: number; hspaceBetween: number; vspaceBetween: number; constructor( width: number, height: number, margin: number, columns: number, rows: number ) { this.margin = margin; this.hspaceBetween = (width - margin * 2) / (columns - 1); this.vspaceBetween = (height - margin * 2) / (rows - 1); } starPoint(ctx: CanvasRenderingContext2D, addr: Coordinate) { ctx.fillStyle = "rgba(0, 0, 0, 1.0);"; const pixel = this.position(addr); ctx.moveTo(pixel.x, pixel.y); ctx.arc(pixel.x, pixel.y, 5, 0, 2 * Math.PI); ctx.fill(); } ghostStone(ctx: CanvasRenderingContext2D, addr: Coordinate, color: Color) { switch (color) { case Color.White: ctx.fillStyle = "rgba(230, 230, 230, 0.5)"; break; case Color.Black: ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; break; } this.drawStone(ctx, addr); } drawStone(ctx: CanvasRenderingContext2D, addr: Coordinate) { const radius = this.hspaceBetween / 2 - 2; const pixel = this.position(addr); ctx.moveTo(pixel.x, pixel.y); ctx.arc(pixel.x, pixel.y, radius, 0, 2.0 * Math.PI); ctx.fill(); } position(addr: Coordinate): Pixel { return { x: this.margin + addr.column * this.hspaceBetween, y: this.margin + addr.row * this.vspaceBetween, }; } address(pixel: Pixel): Coordinate | null { if ( Math.round(pixel.x) < this.margin || Math.round(pixel.y) < this.margin ) { return null; } else { return { column: Math.round( (Math.round(pixel.x) - this.margin) / this.hspaceBetween ), row: Math.round( (Math.round(pixel.y) - this.margin) / this.vspaceBetween ), }; } } } const boardAddress = (size: Size, coordinate: Coordinate): number => coordinate.column + size.width * coordinate.row;