Render the Go board and connect it to the core #42

Merged
savanni merged 5 commits from kifu/render-goban into main 2023-05-16 14:22:20 +00:00
4 changed files with 118 additions and 62 deletions
Showing only changes of commit 140ad0f22c - Show all commits

View File

@ -1,6 +1,6 @@
SOURCES = $(shell find ../core -name "*.rs") SOURCES = $(shell find ../core -name "*.rs")
dist/core.d.ts: $(SOURCES) dist/index.ts: $(SOURCES)
mkdir -p dist mkdir -p dist
typeshare ../core --lang=typescript --output-file=dist/core.d.ts typeshare ../core --lang=typescript --output-file=dist/index.ts

View File

@ -2,7 +2,8 @@
"name": "core-types", "name": "core-types",
"version": "0.0.1", "version": "0.0.1",
"description": "", "description": "",
"types": "dist/core.d.ts", "types": "dist/index.ts",
"main": "dist/index.ts",
"scripts": { "scripts": {
"build": "make", "build": "make",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"

View File

@ -1,10 +1,16 @@
import { BoardElement, Color } from "core-types"; import { BoardElement, Color } from "core-types";
const MARGIN = 20;
const BOARD_WIDTH = 800; const BOARD_WIDTH = 800;
const BOARD_HEIGHT = 800; const BOARD_HEIGHT = 800;
type Pixel = { x: number; y: number };
type Address = { column: number; row: number };
export class GoBoard { export class GoBoard {
private board: BoardElement; private board: BoardElement;
private pen: Pen;
private cursorLocation: Address | null;
canvas: HTMLCanvasElement; canvas: HTMLCanvasElement;
constructor(board: BoardElement) { constructor(board: BoardElement) {
@ -12,10 +18,43 @@ export class GoBoard {
this.canvas = document.createElement("canvas"); this.canvas = document.createElement("canvas");
this.canvas.width = BOARD_WIDTH; this.canvas.width = BOARD_WIDTH;
this.canvas.height = BOARD_HEIGHT; 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();
}
};
} }
setBoard(board: BoardElement) { setBoard(board: BoardElement) {
this.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() { renderBoard() {
@ -25,97 +64,112 @@ export class GoBoard {
return null; return null;
} }
let hspaceBetween = (this.canvas.width - 40) / (this.board.size.width - 1); ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
let vspaceBetween =
(this.canvas.height - 40) / (this.board.size.height - 1);
ctx.lineWidth = 2; ctx.lineWidth = 2;
ctx.strokeStyle = "black"; ctx.strokeStyle = "black";
ctx.beginPath(); ctx.beginPath();
for (var col = 0; col < this.board.size.width; col++) { for (var col = 0; col < this.board.size.width; col++) {
ctx.moveTo(20 + col * hspaceBetween, 20); ctx.moveTo(MARGIN + col * this.pen.hspaceBetween, MARGIN);
ctx.lineTo(20 + col * hspaceBetween, this.canvas.height - 20); 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++) { for (var row = 0; row < this.board.size.height; row++) {
ctx.moveTo(20, 20 + row * vspaceBetween); ctx.moveTo(MARGIN, MARGIN + row * this.pen.vspaceBetween);
ctx.lineTo(this.canvas.width - 20, 20 + row * vspaceBetween); ctx.lineTo(
MARGIN + (this.board.size.width - 1) * this.pen.hspaceBetween,
MARGIN + row * this.pen.vspaceBetween
);
} }
ctx.closePath(); ctx.closePath();
ctx.stroke(); ctx.stroke();
const pen = new Pen(20, 20, hspaceBetween, vspaceBetween); this.pen.starPoint(ctx, { column: 3, row: 3 });
pen.starPoint(ctx, 3, 3); this.pen.starPoint(ctx, { column: 3, row: 9 });
pen.starPoint(ctx, 3, 9); this.pen.starPoint(ctx, { column: 3, row: 15 });
pen.starPoint(ctx, 3, 15); this.pen.starPoint(ctx, { column: 9, row: 3 });
pen.starPoint(ctx, 9, 3); this.pen.starPoint(ctx, { column: 9, row: 9 });
pen.starPoint(ctx, 9, 9); this.pen.starPoint(ctx, { column: 9, row: 15 });
pen.starPoint(ctx, 9, 15); this.pen.starPoint(ctx, { column: 15, row: 3 });
pen.starPoint(ctx, 15, 3); this.pen.starPoint(ctx, { column: 15, row: 9 });
pen.starPoint(ctx, 15, 9); this.pen.starPoint(ctx, { column: 15, row: 15 });
pen.starPoint(ctx, 15, 15);
if (this.cursorLocation) {
this.pen.ghostStone(ctx, this.cursorLocation, Color.White);
}
} }
} }
class Pen { class Pen {
xOffset: number; margin: number;
yOffset: number;
hspaceBetween: number; hspaceBetween: number;
vspaceBetween: number; vspaceBetween: number;
constructor( constructor(
xOffset: number, width: number,
yOffset: number, height: number,
hspaceBetween: number, margin: number,
vspaceBetween: number columns: number,
rows: number
) { ) {
this.xOffset = xOffset; this.margin = margin;
this.yOffset = yOffset; this.hspaceBetween = (width - margin * 2) / (columns - 1);
this.hspaceBetween = hspaceBetween; this.vspaceBetween = (height - margin * 2) / (rows - 1);
this.vspaceBetween = vspaceBetween;
} }
starPoint(ctx: CanvasRenderingContext2D, row: number, col: number) { starPoint(ctx: CanvasRenderingContext2D, addr: Address) {
ctx.moveTo( ctx.fillStyle = "rgba(0, 0, 0, 1.0);";
this.xOffset + col * this.hspaceBetween, const pixel = this.position(addr);
this.yOffset + row * this.vspaceBetween ctx.moveTo(pixel.x, pixel.y);
); ctx.arc(pixel.x, pixel.y, 5, 0, 2 * Math.PI);
ctx.arc(
this.xOffset + col * this.hspaceBetween,
this.yOffset + row * this.vspaceBetween,
5,
0,
2 * Math.PI
);
ctx.fill(); ctx.fill();
} }
ghostStone( ghostStone(ctx: CanvasRenderingContext2D, addr: Address, color: Color) {
ctx: CanvasRenderingContext2D,
row: number,
col: number,
color: Color
) {
switch (color) { switch (color) {
case "White": case Color.White:
ctx.fillStyle = "white"; ctx.fillStyle = "rgba(230, 230, 230, 0.5)";
case "Black": break;
ctx.fillStyle = "black"; case Color.Black:
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
break;
} }
this.drawStone(ctx, row, col); this.drawStone(ctx, addr);
} }
drawStone(ctx: CanvasRenderingContext2D, row: number, col: number) { drawStone(ctx: CanvasRenderingContext2D, addr: Address) {
let radius = this.hspaceBetween / 2 - 2; const radius = this.hspaceBetween / 2 - 2;
let [xLoc, yLoc] = this.stoneLocation(row, col); const pixel = this.position(addr);
ctx.arc(xLoc, yLoc, radius, 0, 2.0 * Math.PI); ctx.moveTo(pixel.x, pixel.y);
ctx.arc(pixel.x, pixel.y, radius, 0, 2.0 * Math.PI);
ctx.fill(); ctx.fill();
} }
stoneLocation(row: number, col: number): [number, number] { position(addr: Address): Pixel {
return [ return {
this.xOffset + col * this.hspaceBetween, x: this.margin + addr.column * this.hspaceBetween,
this.yOffset + col * this.vspaceBetween, y: this.margin + addr.row * this.vspaceBetween,
]; };
}
address(pixel: Pixel): Address | 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
),
};
}
} }
} }

1
package-lock.json generated
View File

@ -36,6 +36,7 @@
} }
}, },
"kifu/pwa": { "kifu/pwa": {
"name": "kifu-pwa",
"version": "1.0.0", "version": "1.0.0",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {