Major overhaul of the architecture
It's pretty horrible to have a commit messing up 90% of the code and also stuff isn't really well organized, but probably it needed to be done...
This commit is contained in:
@@ -5,6 +5,6 @@
|
|||||||
<title>Chess</title>
|
<title>Chess</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="main.js"></script>
|
<script type="module" src="main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
265
frontend/main.js
265
frontend/main.js
@@ -1,131 +1,8 @@
|
|||||||
const BOX_SIDE = 80;
|
import * as visuals from './visuals.js';
|
||||||
const INDEX_MARGIN = BOX_SIDE / 2;
|
|
||||||
const INDEX_LETTERS = 'abcdefgh';
|
|
||||||
|
|
||||||
function createCanvas() {
|
|
||||||
let canvas = document.createElement('canvas');
|
|
||||||
let boardSide = BOX_SIDE * 8 + INDEX_MARGIN;
|
|
||||||
canvas.width = boardSide;
|
|
||||||
canvas.height = boardSide;
|
|
||||||
document.body.appendChild(canvas);
|
|
||||||
return canvas
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawChessBoard(canvas) {
|
|
||||||
let ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// Draw squares
|
|
||||||
ctx.fillStyle = '#c0c0c0';
|
|
||||||
for (let r = 0; r < 8; r++) {
|
|
||||||
for (let c = 0; c < 8; c++) {
|
|
||||||
if ((r + c) % 2) ctx.fillRect(c*BOX_SIDE + INDEX_MARGIN,
|
|
||||||
r*BOX_SIDE + INDEX_MARGIN,
|
|
||||||
BOX_SIDE, BOX_SIDE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw letters
|
|
||||||
ctx.fillStyle = 'black';
|
|
||||||
let fontsize = INDEX_MARGIN / 2;
|
|
||||||
ctx.font = `${fontsize}px Monospace`;
|
|
||||||
for (let idx = 0; idx < 8; idx++) {
|
|
||||||
ctx.fillText(INDEX_LETTERS[idx],
|
|
||||||
BOX_SIDE*idx + BOX_SIDE/2 + INDEX_MARGIN - fontsize / 4,
|
|
||||||
INDEX_MARGIN / 2);
|
|
||||||
ctx.fillText((8-idx).toString(),
|
|
||||||
INDEX_MARGIN / 2 - fontsize / 4,
|
|
||||||
BOX_SIDE*idx + BOX_SIDE/2 + INDEX_MARGIN + fontsize / 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeScreenCoords(position, canvas) {
|
|
||||||
let canvasRect = canvas.getBoundingClientRect();
|
|
||||||
let rc = boardIndexToRowCol(position);
|
|
||||||
let topPx = canvasRect.top + INDEX_MARGIN + rc[0]*BOX_SIDE + window.pageYOffset;
|
|
||||||
let leftPx = canvasRect.left + INDEX_MARGIN + rc[1]*BOX_SIDE + window.pageXOffset;
|
|
||||||
return [topPx, leftPx]
|
|
||||||
}
|
|
||||||
|
|
||||||
class Move {
|
|
||||||
constructor(kind, from, to) {
|
|
||||||
this.kind = kind;
|
|
||||||
this.from = from;
|
|
||||||
this.to = to;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Piece {
|
|
||||||
constructor(who, color, position) {
|
|
||||||
this.who = who;
|
|
||||||
this.color = color;
|
|
||||||
this.position = position;
|
|
||||||
this._im = null;
|
|
||||||
}
|
|
||||||
icon() {
|
|
||||||
return `./icons/${this.who}_${this.color}.svg`
|
|
||||||
}
|
|
||||||
draw(canvas) {
|
|
||||||
let [topPx, leftPx] = computeScreenCoords(this.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Configuration {
|
|
||||||
constructor(pieces) {
|
|
||||||
this.pieces = pieces
|
|
||||||
}
|
|
||||||
draw(canvas) {
|
|
||||||
for (let piece of this.pieces) {
|
|
||||||
piece.draw(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getAt(position) {
|
|
||||||
for (let piece of this.pieces) {
|
|
||||||
if (piece.position === position) return piece;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
dropAt(position) {
|
|
||||||
for (let idx = 0; idx < this.pieces.length; idx++) {
|
|
||||||
if (this.pieces[idx].position === position) {
|
|
||||||
this.pieces[idx].undraw();
|
|
||||||
this.pieces.splice(idx, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
makeMove(move, canvas) {
|
|
||||||
let piece = this.getAt(move.from);
|
|
||||||
if (piece === null) return;
|
|
||||||
this.dropAt(move.to);
|
|
||||||
piece.position = move.to;
|
|
||||||
piece.draw(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupBoard() {
|
function setupBoard() {
|
||||||
let pawns = [];
|
let pawns = [];
|
||||||
for (let x of INDEX_LETTERS) {
|
for (let x of 'abcdefgh') {
|
||||||
pawns.push(new Piece('pawn', 'black', `${x}7`),
|
pawns.push(new Piece('pawn', 'black', `${x}7`),
|
||||||
new Piece('pawn', 'white', `${x}2`));
|
new Piece('pawn', 'white', `${x}2`));
|
||||||
}
|
}
|
||||||
@@ -149,30 +26,122 @@ function setupBoard() {
|
|||||||
new Piece('bishop', 'white', 'f1'),
|
new Piece('bishop', 'white', 'f1'),
|
||||||
new Piece('queen', 'white', 'd1'),
|
new Piece('queen', 'white', 'd1'),
|
||||||
new Piece('king', 'white', 'e1'),
|
new Piece('king', 'white', 'e1'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Backend {
|
||||||
|
constructor() {
|
||||||
|
this.config = setupBoard();
|
||||||
|
}
|
||||||
|
getConfig() {
|
||||||
|
return this.config.clone();
|
||||||
|
}
|
||||||
|
makeMove(move) {
|
||||||
|
this.config = this.config.makeMove(move);
|
||||||
|
return this.config.clone();
|
||||||
|
}
|
||||||
|
getAvailableMoves(position) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Chess {
|
||||||
|
constructor() {
|
||||||
|
this.backend = new Backend();
|
||||||
|
this.configVis = null;
|
||||||
|
this.canvas = new visuals.Canvas();
|
||||||
|
this.moveSource = null;
|
||||||
|
this.canMoveTo = [];
|
||||||
|
this.syncBackend();
|
||||||
|
document.onclick = (ev) => this.click(ev);
|
||||||
|
console.log(this.moveSource);
|
||||||
|
}
|
||||||
|
syncBackend() {
|
||||||
|
let config = this.backend.getConfig();
|
||||||
|
if (this.configVis !== null) {
|
||||||
|
this.configVis.undraw();
|
||||||
|
}
|
||||||
|
this.configVis = new visuals.ConfigVis(config.pieces);
|
||||||
|
this.configVis.draw(this.canvas);
|
||||||
|
}
|
||||||
|
showAvailableMoves(canMoveTo) {
|
||||||
|
}
|
||||||
|
click(ev) {
|
||||||
|
console.log(this);
|
||||||
|
if (this.moveSource === null) {
|
||||||
|
this._firstClick(ev);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._secondClick(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_firstClick(ev) {
|
||||||
|
let rcA = this.canvas.screenCoordsToRowCol(ev.clientX, ev.clientY);
|
||||||
|
if (rcA === null) return;
|
||||||
|
let positionA = visuals.rowColToBoardIndex(...rcA);
|
||||||
|
this.showAvailableMoves(this.backend.getAvailableMoves(positionA));
|
||||||
|
this.moveSource = positionA;
|
||||||
|
}
|
||||||
|
_secondClick(ev) {
|
||||||
|
let rcB = this.canvas.screenCoordsToRowCol(ev.clientX, ev.clientY);
|
||||||
|
if (rcB !== null) {
|
||||||
|
let move = {
|
||||||
|
from: this.moveSource,
|
||||||
|
to: visuals.rowColToBoardIndex(...rcB),
|
||||||
|
}
|
||||||
|
this.backend.makeMove(move);
|
||||||
|
this.syncBackend();
|
||||||
|
}
|
||||||
|
this.moveSource = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Move {
|
||||||
|
constructor(kind, from, to) {
|
||||||
|
this.kind = kind;
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Piece {
|
||||||
|
constructor(who, color, position) {
|
||||||
|
this.who = who;
|
||||||
|
this.color = color;
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
clone() {
|
||||||
|
return new Piece(this.who, this.color, this.position);
|
||||||
|
}
|
||||||
|
moveTo(position) {
|
||||||
|
return new Piece(this.who, this.color, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Configuration {
|
||||||
|
constructor(pieces) {
|
||||||
|
this.pieces = pieces
|
||||||
|
}
|
||||||
|
clone() {
|
||||||
|
let pieces = [];
|
||||||
|
for (let piece of this.pieces) {
|
||||||
|
pieces.push(piece.clone());
|
||||||
|
}
|
||||||
|
return new Configuration(pieces);
|
||||||
|
}
|
||||||
|
getAt(position) {
|
||||||
|
return this.pieces.find(piece => piece.position === position) || null;
|
||||||
|
}
|
||||||
|
dropAt(position) {
|
||||||
|
return this.pieces.filter(piece => piece.position !== position);
|
||||||
|
}
|
||||||
|
makeMove(move) {
|
||||||
|
const pieceToMove = this.getAt(move.from);
|
||||||
|
if (pieceToMove === null) return this;
|
||||||
|
else return new Configuration([
|
||||||
|
...this.dropAt(move.from), pieceToMove.moveTo(move.to)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
function boardIndexToRowCol(boardIndex) {
|
|
||||||
let colLetter = boardIndex[0];
|
|
||||||
let rowDigit = boardIndex[1];
|
|
||||||
let row = 8 - parseInt(rowDigit);
|
|
||||||
let col = INDEX_LETTERS.indexOf(colLetter);
|
|
||||||
return [row, col];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function demo() {
|
new Chess();
|
||||||
let canvas = createCanvas();
|
|
||||||
drawChessBoard(canvas);
|
|
||||||
let config = setupBoard();
|
|
||||||
config.draw(canvas);
|
|
||||||
await new Promise(r => setTimeout(r, 6000));
|
|
||||||
config.makeMove({'kind': 'xxx', 'from': 'e2', 'to': 'e4'}, canvas);
|
|
||||||
await new Promise(r => setTimeout(r, 2000));
|
|
||||||
config.makeMove({'kind': 'xxx', 'from': 'e7', 'to': 'e5'}, canvas);
|
|
||||||
await new Promise(r => setTimeout(r, 2000));
|
|
||||||
config.makeMove({'kind': 'xxx', 'from': 'd2', 'to': 'd4'}, canvas);
|
|
||||||
await new Promise(r => setTimeout(r, 2000));
|
|
||||||
config.makeMove({'kind': 'xxx', 'from': 'e5', 'to': 'd4'}, canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
demo();
|
|
||||||
|
|||||||
123
frontend/visuals.js
Normal file
123
frontend/visuals.js
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
const BOX_SIDE = 80;
|
||||||
|
const INDEX_MARGIN = BOX_SIDE / 2;
|
||||||
|
const INDEX_LETTERS = 'abcdefgh';
|
||||||
|
|
||||||
|
export class Canvas {
|
||||||
|
constructor() {
|
||||||
|
this._canvas = document.createElement('canvas');
|
||||||
|
let boardSide = BOX_SIDE * 8 + INDEX_MARGIN;
|
||||||
|
this._canvas.width = boardSide;
|
||||||
|
this._canvas.height = boardSide;
|
||||||
|
document.body.appendChild(this._canvas);
|
||||||
|
this.drawChessBoard();
|
||||||
|
}
|
||||||
|
drawChessBoard() {
|
||||||
|
// Draw squares
|
||||||
|
let ctx = this._canvas.getContext('2d');
|
||||||
|
ctx.fillStyle = '#c0c0c0';
|
||||||
|
for (let r = 0; r < 8; r++) {
|
||||||
|
for (let c = 0; c < 8; c++) {
|
||||||
|
if ((r + c) % 2) ctx.fillRect(c * BOX_SIDE + INDEX_MARGIN,
|
||||||
|
r * BOX_SIDE + INDEX_MARGIN,
|
||||||
|
BOX_SIDE, BOX_SIDE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw letters
|
||||||
|
ctx.fillStyle = 'black';
|
||||||
|
let fontsize = INDEX_MARGIN / 2;
|
||||||
|
ctx.font = `${fontsize}px Monospace`;
|
||||||
|
for (let idx = 0; idx < 8; idx++) {
|
||||||
|
ctx.fillText(INDEX_LETTERS[idx],
|
||||||
|
BOX_SIDE * idx + BOX_SIDE / 2 + INDEX_MARGIN - fontsize / 4,
|
||||||
|
INDEX_MARGIN / 2);
|
||||||
|
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 PieceVis {
|
||||||
|
constructor(piece) {
|
||||||
|
this.icon = `./icons/${piece.who}_${piece.color}.svg`;
|
||||||
|
this.position = piece.position;
|
||||||
|
this.im = null;
|
||||||
|
}
|
||||||
|
draw(canvas) {
|
||||||
|
let [topPx, leftPx] = canvas.rowColToScreenCoords(
|
||||||
|
...boardIndexToRowCol(this.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 (const piece of this.configuration) {
|
||||||
|
let pieceVis = new PieceVis(piece);
|
||||||
|
pieceVis.draw(canvas);
|
||||||
|
this.piecesVis.set(piece.position, pieceVis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
undraw() {
|
||||||
|
for (let [position, pieceVis] of this.piecesVis) {
|
||||||
|
pieceVis.undraw();
|
||||||
|
}
|
||||||
|
this.piecesVis.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user