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