Thus make all state management the engine's responsibility, and remove the state maintaining code from frontend!
191 lines
5.1 KiB
JavaScript
191 lines
5.1 KiB
JavaScript
const BOX_SIDE = 80;
|
|
const INDEX_MARGIN = BOX_SIDE / 2;
|
|
const INDEX_LETTERS = "abcdefgh";
|
|
|
|
export class Canvas {
|
|
constructor() {
|
|
this._canvas = document.createElement("canvas");
|
|
this._ctx = this._canvas.getContext("2d");
|
|
let boardSide = BOX_SIDE * 8 + INDEX_MARGIN;
|
|
this._canvas.width = boardSide;
|
|
this._canvas.height = boardSide;
|
|
document.body.appendChild(this._canvas);
|
|
this.drawChessBoard();
|
|
}
|
|
decideColor(r, c) {
|
|
if ((r + c) % 2) return "#c0c0c0";
|
|
else return "#ffffff";
|
|
}
|
|
drawSquareColor(color, r, c) {
|
|
const crtStyle = this._ctx.fillStyle;
|
|
this._ctx.fillStyle = color;
|
|
this._ctx.fillRect(
|
|
c * BOX_SIDE + INDEX_MARGIN,
|
|
r * BOX_SIDE + INDEX_MARGIN,
|
|
BOX_SIDE,
|
|
BOX_SIDE
|
|
);
|
|
this._ctx.fillStyle = crtStyle;
|
|
}
|
|
drawChessBoard() {
|
|
// Draw squares
|
|
for (let r = 0; r < 8; r++) {
|
|
for (let c = 0; c < 8; c++) {
|
|
this.drawSquareColor(this.decideColor(r, c), r, c);
|
|
}
|
|
}
|
|
|
|
// Draw letters
|
|
this._ctx.fillStyle = "black";
|
|
let fontsize = INDEX_MARGIN / 2;
|
|
this._ctx.font = `${fontsize}px Monospace`;
|
|
for (let idx = 0; idx < 8; idx++) {
|
|
this._ctx.fillText(
|
|
INDEX_LETTERS[idx],
|
|
BOX_SIDE * idx + BOX_SIDE / 2 + INDEX_MARGIN - fontsize / 4,
|
|
INDEX_MARGIN / 2
|
|
);
|
|
this._ctx.fillText(
|
|
(8 - idx).toString(),
|
|
INDEX_MARGIN / 2 - fontsize / 4,
|
|
BOX_SIDE * idx + BOX_SIDE / 2 + INDEX_MARGIN + fontsize / 4
|
|
);
|
|
}
|
|
}
|
|
screenCoordsToRowCol(cx, cy) {
|
|
let canvasRect = this._canvas.getBoundingClientRect();
|
|
if (cx > canvasRect.right || cy > canvasRect.bottom) {
|
|
return null;
|
|
}
|
|
let canvasX = cx - canvasRect.left;
|
|
let canvasY = cy - canvasRect.top;
|
|
let row = Math.floor((canvasY - INDEX_MARGIN) / BOX_SIDE);
|
|
let col = Math.floor((canvasX - INDEX_MARGIN) / BOX_SIDE);
|
|
return [row, col];
|
|
}
|
|
rowColToScreenCoords(row, col) {
|
|
let canvasRect = this._canvas.getBoundingClientRect();
|
|
let topPx =
|
|
canvasRect.top + INDEX_MARGIN + row * BOX_SIDE + window.pageYOffset;
|
|
let leftPx =
|
|
canvasRect.left + INDEX_MARGIN + col * BOX_SIDE + window.pageXOffset;
|
|
return [topPx, leftPx];
|
|
}
|
|
}
|
|
|
|
function boardIndexToRowCol(boardIndex) {
|
|
let [colLetter, rowDigit] = boardIndex;
|
|
let row = 8 - parseInt(rowDigit);
|
|
let col = INDEX_LETTERS.indexOf(colLetter);
|
|
return [row, col];
|
|
}
|
|
|
|
export function rowColToBoardIndex(row, col) {
|
|
let rowDigit = 8 - row;
|
|
let colLetter = INDEX_LETTERS[col];
|
|
return `${colLetter}${rowDigit}`;
|
|
}
|
|
|
|
export class ActiveSquares {
|
|
constructor() {
|
|
this.selectedSquare = null;
|
|
this.moveSquares = [];
|
|
}
|
|
selectSquare(canvas, position) {
|
|
let [r, c] = boardIndexToRowCol(position);
|
|
canvas.drawSquareColor("#c0c0ff", r, c);
|
|
this.selectedSquare = position;
|
|
}
|
|
setMoveSquares(canvas, moveSquares) {
|
|
moveSquares.forEach((position) => {
|
|
let [r, c] = boardIndexToRowCol(position);
|
|
canvas.drawSquareColor("#ffffc0", r, c);
|
|
this.moveSquares.push(position);
|
|
});
|
|
}
|
|
unsetMoveSquares(canvas) {
|
|
this.moveSquares.forEach((position) => {
|
|
let [r, c] = boardIndexToRowCol(position);
|
|
canvas.drawSquareColor(canvas.decideColor(r, c), r, c);
|
|
});
|
|
this.moveSquares = [];
|
|
}
|
|
unselectSquare(canvas) {
|
|
if (this.selectedSquare === null) return;
|
|
let [r, c] = boardIndexToRowCol(this.selectedSquare);
|
|
canvas.drawSquareColor(canvas.decideColor(r, c), r, c);
|
|
this.selectedSquare = null;
|
|
}
|
|
}
|
|
|
|
class PieceVis {
|
|
constructor(piece) {
|
|
this.piece = piece;
|
|
this.im = null;
|
|
}
|
|
get ["icon"]() {
|
|
return `./icons/${this.piece.piece_type}_${this.piece.color}.svg`;
|
|
}
|
|
draw(canvas, position) {
|
|
let [topPx, leftPx] = canvas.rowColToScreenCoords(
|
|
...boardIndexToRowCol(position),
|
|
canvas
|
|
);
|
|
if (this.im === null) {
|
|
let im = new Image(BOX_SIDE, BOX_SIDE);
|
|
im.src = this.icon;
|
|
im.onload = () => {
|
|
im.style.position = "absolute";
|
|
im.style.top = `${topPx}px`;
|
|
im.style.left = `${leftPx}px`;
|
|
};
|
|
document.body.appendChild(im);
|
|
this.im = im;
|
|
} else {
|
|
this.im.style.top = `${topPx}px`;
|
|
this.im.style.left = `${leftPx}px`;
|
|
}
|
|
}
|
|
undraw() {
|
|
if (this.im !== null) {
|
|
this.im.remove();
|
|
this.im = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class ConfigVis {
|
|
constructor(configuration) {
|
|
this.configuration = configuration;
|
|
this.piecesVis = new Map();
|
|
}
|
|
draw(canvas) {
|
|
for (let [position, piece] of this.configuration) {
|
|
if (this.piecesVis.has(position)) {
|
|
if (this.piecesVis.get(position).piece == piece) {
|
|
continue;
|
|
}
|
|
}
|
|
let pv = this.piecesVis.get(position);
|
|
if (pv !== undefined) {
|
|
pv.undraw();
|
|
}
|
|
pv = new PieceVis(piece);
|
|
pv.draw(canvas, position);
|
|
this.piecesVis.set(position, pv);
|
|
}
|
|
for (let [position, pv] of this.piecesVis) {
|
|
if (!this.configuration.has(position)) {
|
|
pv.undraw();
|
|
this.piecesVis.delete(position);
|
|
}
|
|
}
|
|
}
|
|
undraw() {
|
|
for (let pv of this.piecesVis.values()) {
|
|
pv.undraw();
|
|
}
|
|
this.piecesVis.clear();
|
|
}
|
|
}
|