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
3 changed files with 113 additions and 12 deletions
Showing only changes of commit 6c87d23537 - Show all commits

View File

@ -0,0 +1 @@
export const assertNever = (_: never) => {};

View File

@ -1,19 +1,25 @@
import { BoardElement, Color } from "core-types";
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 Address = { column: number; row: 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: Address | null;
private cursorLocation: Coordinate | null;
canvas: HTMLCanvasElement;
constructor(board: BoardElement) {
constructor({ board, onClick }: GoBoardProps) {
this.board = board;
this.canvas = document.createElement("canvas");
this.canvas.width = BOARD_WIDTH;
@ -43,9 +49,30 @@ export class GoBoard {
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(
@ -119,7 +146,7 @@ class Pen {
this.vspaceBetween = (height - margin * 2) / (rows - 1);
}
starPoint(ctx: CanvasRenderingContext2D, addr: Address) {
starPoint(ctx: CanvasRenderingContext2D, addr: Coordinate) {
ctx.fillStyle = "rgba(0, 0, 0, 1.0);";
const pixel = this.position(addr);
ctx.moveTo(pixel.x, pixel.y);
@ -127,7 +154,7 @@ class Pen {
ctx.fill();
}
ghostStone(ctx: CanvasRenderingContext2D, addr: Address, color: Color) {
ghostStone(ctx: CanvasRenderingContext2D, addr: Coordinate, color: Color) {
switch (color) {
case Color.White:
ctx.fillStyle = "rgba(230, 230, 230, 0.5)";
@ -140,7 +167,7 @@ class Pen {
this.drawStone(ctx, addr);
}
drawStone(ctx: CanvasRenderingContext2D, addr: Address) {
drawStone(ctx: CanvasRenderingContext2D, addr: Coordinate) {
const radius = this.hspaceBetween / 2 - 2;
const pixel = this.position(addr);
ctx.moveTo(pixel.x, pixel.y);
@ -148,14 +175,14 @@ class Pen {
ctx.fill();
}
position(addr: Address): Pixel {
position(addr: Coordinate): Pixel {
return {
x: this.margin + addr.column * this.hspaceBetween,
y: this.margin + addr.row * this.vspaceBetween,
};
}
address(pixel: Pixel): Address | null {
address(pixel: Pixel): Coordinate | null {
if (
Math.round(pixel.x) < this.margin ||
Math.round(pixel.y) < this.margin
@ -173,3 +200,6 @@ class Pen {
}
}
}
const boardAddress = (size: Size, coordinate: Coordinate): number =>
coordinate.column + size.width * coordinate.row;

View File

@ -1,21 +1,91 @@
import { GoBoard } from "./components/Board";
import { CoreRequest, CoreResponse } from "core-types";
import { CoreApi, initCore } from "./coreApi";
import { assertNever } from "./assertNever";
/*
const processResponse = (response: CoreResponse) => {
const root = document.getElementById("root");
if (!root) {
return;
}
};
*/
class UIState {
private currentView: GoBoard | null;
private rootElement: HTMLElement;
coreApi: CoreApi;
constructor(coreApi: CoreApi, root: HTMLElement) {
this.currentView = null;
this.rootElement = root;
this.coreApi = coreApi;
if (!root) {
console.log("root element not found");
return;
}
}
processResponse(response: CoreResponse) {
switch (response.type) {
case "PlayingFieldView":
if (this.currentView) {
this.currentView.setBoard(response.content.board);
} else {
this.currentView = new GoBoard({
board: response.content.board,
onClick: async (request: CoreRequest) => {
const response = await this.coreApi.dispatch(request);
this.processResponse(response);
},
});
this.rootElement?.appendChild(this.currentView.canvas);
this.currentView.renderBoard();
}
break;
default:
console.log("impossible branch: ", response);
alert("impossible branch");
// assertNever(response);
break;
}
}
}
const main = async () => {
let coreApi = await initCore();
let response = await coreApi.playingField();
let response = await coreApi.dispatch({ type: "PlayingField" });
const root = document.getElementById("root");
if (!root) {
console.log("root element not present");
return;
}
const uiState = new UIState(coreApi, root);
uiState.processResponse(response);
/*
console.log("playing field response: ", response);
const root = document.getElementById("root");
if (!root) {
alert("could not retrieve the app root container");
return;
}
const board = new GoBoard(response.board);
const board = new GoBoard({
board: response.content.board,
onClick: async (request: CoreRequest) => {
const response = await coreApi.dispatch(request);
},
});
root.appendChild(board.canvas);
console.log("constructed board: ", board);
board.renderBoard();
*/
};
main();