Commit 89cd07c2 authored by Gerson Sunyé's avatar Gerson Sunyé
Browse files

Projet-2019 - Initial commit

parents
build/
node_modules/
.vscode
package-lock.json
\ No newline at end of file
= Online Chess
Ce projet consiste à déployer une application web de jeu d'échecs jouable en multijoueur.
== Objectifs
La fin du semestre approche, il est temps de montrer tout ce que vous avez appris et de proposer au monde entier votre première application web (webapp) !
L'objectif de ce mini-projet est d'intégrer et adapter tout le travail réalisé lors des TP et TD précédents, afin de réaliser une application de jeu d'échecs jouable (et observable) en multijoueur réseau.
== Préparation
. Créez une divergence (en anglais, _fork_) du projet sur votre compte GitLab: https://gitlab.univ-nantes.fr/naomod/idl/projet-2019/forks/new[Cliquez ici pour créer le Fork] (Éventuellement, Gitlab vous demandera de vous connecter).
. Créez et configurez une copie locale du projet. Ouvrez le *Terminal* et exécutez les commandes suivantes:
[source,bash]
----
git clone https://gitlab.univ-nantes.fr/${USER}/projet-2019.git
cd projet-2019
npm install
----
. Regardez la structure du projet. Le projet est organisé en différents dossiers:
[source,txt]
----
|-- onlineChess
|-- client
|-- index.html
|-- script.js
|-- style.css
|-- src
|-- chessboard.ts
|-- main.ts
|-- move-validation.ts
|-- movements.ts
|-- piece.ts
|-- position.ts
|-- spec
|-- move-validation-spec.ts
|-- movements-spec.ts
|-- node_modules
|-- package.json
|-- tsconfig.json
----
* `client` contient le code Javascript qui sera exécuté sur le browser. Vous ne devez pas modifier le contenu de ce dossier.
* `index.html` : page principale de l'application
* `style.css` : mise en forme de l'application
* `script.js` : algorithme(s) JavaScript côté client (affichage de l'échiquier)
* `src` contient le code source du serveur.
* `main.ts` : programme principal de création et gestion du serveur web
* `spec` contient les tests unitaires du serveur.
* `node_modules` contient les modules Node.js utilisés dans le projet. Vous ne devez pas modifier le contenu de ce dossier.
* `package.json` est le fichier de configuration de *npm*. Vous n'avez pas besoin de le modifier.
* `tsconfig.json` est le fichier de configuration de *TypeScript*. Vous n'avez pas besoin de le modifier.
== Test et lancement
* Le projet utilise l'outil de construction et de gestion de modules *npm*.
* Pour lancer tous les tests unitaires du projet avec Alsatian, exécutez: `npm test`.
* Pour lancer le serveur en mode développement, exécutez: `npm run dev`.
* Pour accéder à l'application, ouvrez l'URL suivante: http://localhost:8080.
* Pour accéder au contenu JSON de l'échiquier en cours, utilisez l'URL suivante: http://localhost:8080/status.js.
== Manuel d'utilisation
Pour déplacer les pièces sur l'échiquier, indiquez dans le formulaire en bas de page la pièce à déplacer et sa destination.
Utilisez la notation par coordonnées, qui inclut la place à partir de laquelle la pièce se déplace, ainsi que sa destination.
Par exemple:
|===
|Coup |Coordonnées |Description
| 1. |E2-E4 E7-E5 |Pion blanc en E2 se déplace à E4. Pion noir en E7 se déplace à E5.
| 2. |G1-F3 B8-C6 |Cheval blanc en G1 se déplace à F3. Cheval noir en B8 se déplace à C6.
|===
== Fonctionnement de l'application
Le programme principal du serveur (`main.ts`) est chargé de démarrer un mini-serveur web capable de recevoir les différentes requêtes provenant des navigateurs connectés à l'application :
* GET "`/`" : distribue le fichier `views/index.ejs`;
* GET "`/status.js`" : génère et distribue l'échiquier en cours au format JSON.
* POST "`/`" : reçoit et traite un coup à jouer;
Ces trois traitements correspondent aux différents appels à `app.get()` et `app.post()` du programme principal.
== Chronologie d'une partie
. Lorsqu'un utilisateur se connecte à l'application (adresse *"/"*), le serveur distribue alors la page html principale composée d'un échiquier vierge et d'une zone de saisie permettant à l'utilisateur de remplir le coup à jouer.
. Le navigateur internet récupère immédiatement les informations de la partie en cours présentes à l'adresse `/status.js` et remplit l'échiquier à l'aide d'un script situé dans le fichier `script.js`. Ces deux scripts se trouvent dans le dossier `client`.
. Un clic sur le bouton "Envoyer" effectue une requête de type *POST* au à l'adresse *"/"* du serveur, contenant les informations du champs de texte associé.
Le serveur traite alors la requête afin de jouer le coup demandé.
. La page internet du joueur est alors rechargée automatiquement, affichant ainsi le nouvel état de la partie.
. etc…
== Travail à réaliser
=== Validation des mouvements
La version actuelle permet le déplacement libre des pièces, sans respecter les règles des échecs.
Pour l'instant, seuls les déplacements des pions sont validés.
Vous devez mettre en oeuvre la validations des déplacements des autres pièces: le Roi, la Dame, le Cavalier, le Fou et la Tour.
Le traitement des déplacements se fait de la façon suivante:
. Lorsqu'une requête *POST* arrive, le serveur extrait la valeur du champ envoyé et appelle la fonction `processMove()` du module `movements`.
. La fonction `processMove()` appelle une autre fonction, `parseMoveString()`, qui transforme une chaîne de caractères en un déplacement (`interface Move`) entre 2 positions (`interface Position`).
. La fonction `processMove()` appelle ensuite la fonction `isMovePossible()`, qui fait appel à différentes fonctions de validation spécifiques aux pièces de l'échiquier (une par type de pièce). Le module `move-validation` contient toutes les fonctions de validation de déplacements.
. Par exemple, lorsqu'il s'agit d'un Pion blanc, la fonction `isMovePossible()` appelle la fonction `whitePawnMove()`, qui retourne `true` si le déplacement est possible ou `false` si ce n'est pas le cas.
. Si le mouvement est possible, c'est à dire la fonction `isMovePossible()` retourne `true`, la fonction `processMove()` appelle la fonction `performMove(), qui effectue le déplacement.
Vous devez donc parcourir le module `move-validation` et implémenter les fonctions de validation contenant le commentaire "`// #TODO:`".
=== Tests unitaires
Pour vérifier que les fonctions du module `move-validation` fonctionnent correctement, vous devez écrire des tests unitaires, qui vont vérifier que les fonctions acceptent les mouvements possibles et n'acceptent pas les mouvements impossibles.
Les mouvements sont possibles (ou impossibles) en accord avec les https://fr.wikipedia.org/wiki/Échecs[règles des échecs].
Comme ces règles sont complexes, vous serez mené à écrire plusieurs tests unitaires pour vérifier les mouvements possibles et impossibles d'une même pièce.
Les signatures des fonctions du module `move-validation` suivent la même convention :
[source,ts]
----
function colorPieceMove(board: Chessboard, move: Move): boolean
----
Le paramètre `board` contient l'échiquier de la partie en cours et `move` contient le déplacement demandé par le joueur à travers le browser.
Le paramètre `move` contient 2 coordonnées de type `Position`, représentant le début et la fin du déplacement.
Les coordonnées indiquent *toujours* des cases à l'intérieur de l'échiquier, c'est à dire, une colonne entre `A` et `H` et une ligne entre `1` et `8`.
Donc, il n'y a pas besoin de vérifier si un déplacement conduit une pièce à l'extérieur de l'échiquier.
Les tests unitaires des fonctions `blackPawnMove()` et `whitePawnMove()` ont déjà été implémentés, vous les trouverez dans le fichier `./spec/move-validation-spec.ts`.
*Vous devez compléter tous les squelettes de tests unitaires fournis à l'intérieur de ce fichier !*
Vous devez procéder par itérations successives, n'essayez pas d'implémenter les fonctions d'un seul trait. Observez le cycle de développement suivant:
. Implémentez une fonctionnalité simple.
. Écrivez le ou les tests unitaires qui vérifient cette fonctionnalité.
. Exécutez les tests pour vérifier que la fonctionnalité marche correctement et la non-régression.
. Recommencez avec la fonctionnalité suivante.
Par exemple, lorsque vous allez implémenter les fonctions qui valident le mouvement des tours (`blackRookMove()` et `whiteRookMove()`) , vous pouvez subdiviser leurs comportements en différentes fonctionnalités:
* Validation des mouvements horizontaux, sans se préoccuper des autres pièces.
* Validation des mouvements verticaux, toujours sans se préoccuper des autres pièces.
* Invalidation d'des mouvements (horizontaux et verticaux) lorsque la case finale contient une pièce de même couleur.
* Validation des mouvements (horizontaux et verticaux) qui se terminent sur une case contenant une pièce d'une couleur différente.
* Invalidation des mouvements (horizontaux et verticaux) lorsque toutes les cases intermédiaires ne sont pas vides.
=== Exemple: validation des mouvements d'une tour en plusieurs étapes
==== Etape 1
Commencez par la 1e fonctionnalité, la validation des déplacements horizontaux:
[source,ts]
----
// Dans le fichier "move-validation.ts"
export function rookMove(board: Chessboard, move: Move): boolean {
return move.from.rank === move.to.rank; // Si les lignes de début de fin sont les mêmes, le déplacement est horizontal
}
----
Écrivez ensuite le test unitaire pour cette fonctionnalité:
[source,ts]
----
// Dans le fichier "move-validation-spec.ts"
describe("Test rookMove()", () => {
// Fonction exécutée avant chaque test unitaires:
beforeEach( () => {
// Création d'un échiquier vide:
chessboard = createEmptyChessboard();
// La variable "positionE4" a été créée au début du module pour simplifier le code des tests
// Place une tour sur la case E4 d'un échiquier vide:
putPiece(chessboard, positionE4, pieces.blackPawn);
});
it("A roock can move horizontally", () => {
// Les variable "moveE4_H4" et "moveE4_14" ont été créées au début
// du module pour simplifier le code des tests.
// Le déplacement doit être possible:
expect(isPossible.rookMove(chessboard, moveE4_H4)).toBeTruthy();
expect(isPossible.rookMove(chessboard, moveE4_A4)).toBeTruthy();
});
----
==== Etape 2
Nouvelle fonctionnalité à implémenter: la validation des déplacements verticaux. Modifiez la fonction `rookMove()`:
[source,ts]
----
// Dans le fichier "move-validation.ts"
export function rookMove(board: Chessboard, move: Move): boolean {
return move.from.rank === move.to.rank || // Si les lignes de début de fin sont les mêmes, le déplacement est horizontal
move.from.file === move.to.file; // Si les colonnes de début de fin sont les mêmes, le déplacement est vertical
}
----
Écrivez ensuite un nouveau test unitaire pour cette nouvelle fonctionnalité:
[source,ts]
----
// Dans le fichier "move-validation-spec.ts"
describe("Test rookMove()", () => {
beforeEach( () => { // Fonction exécutée avant chaque test unitaires
chessboard = createEmptyChessboard(); // Création d'un échiquier vide
});
it("A roock can move horizontally", () => { // (...)
});
it("A roock can move vertically", () => {
expect(isPossible.rookMove(chessboard, moveE4_E8)).toBeTruthy();
expect(isPossible.rookMove(chessboard, moveE4_E1)).toBeTruthy();
});
----
==== Autres étapes
Suivez la même démarche pour implémenter et tester les autres fonctionnalités, c'est à dire, les autres mouvements possibles des tours.
=== Rendu
Vous allez pouvoir effectuer le rendu directement depuis l'interface de Gitlab, en réalisant ce qu'on appelle une *demande de fusion*.
Cela permet de nous envoyer tous les changements que vous avez effectué sur le projet en quelques clics.
. Assurez vous d'avoir effectué tous les _commits_ et _pushs_ nécessaires avec git.
. Dans le panneau de gauche, cliquez sur "Demandes de fusion".
. Cliquez sur "Nouvelle demande de fusion".
. Vérifiez que dans la partie droite on trouve bien `naomod/software-development-course/onlineChess` et `master`.
. Dans la partie gauche, choisissez `<votre nom d'utilisateur>/onlineChess` (normalement déjà choisi) et également `master`.
. Cliquez sur "Compare branches and continue".
. Comme titre pour la demande de fusion, indiquez "Rendu NOM1 NOM2".
. Enfin, cliquez en bas sur "Submit demande de fusion"
Vous atteignez alors une page qui résume la demande effectuée.
Nous vous recommandons alors de cliquer sur l'onglet "_Changes_" afin d'avoir accès une une représentation visuelle de tous les changements que vous avez effectué.
Les lignes rouges indiques ce que vous avez retiré, les lignes vertes indiquent ce que vous avez ajouté.
Vérifiez si tout votre travail réalisé est bien présent sous la forme de lignes vertes.
Si vous le souhaitez, vous pouvez ajouter un fichier "`RENDU.md`" à la racine du projet, afin de décrire les spécificités de votre projet (choix techniques, parties non traitées, extensions non demandées, etc.).
=== Derniers conseils
* Rappelez-vous que « _Une fonction sans test unitaire ne fonctionne pas_ » !
* Rappelez-vous aussi que «*N'importe qui peut écrire du code compréhensible par les ordinateurs, mais seulement les bon développeurs parviennent à écrire du code intelligible par les humains* » !
* Écrivez les tests unitaires avant ou en même temps que les fonctions. Ne les laissez pas pour la fin, les test unitaires sont très utiles pendant le développement et vous feront gagner du temps.
* Faites bon usage de `git` : effectuez des _commits_ et des _pushs_ régulièrement ! Cela vous permet d'éviter de perdre votre travail, et de mieux collaborer en équipe.
{
"name": "chess",
"description": "Ce projet consiste à déployer une application web de jeu d'échecs jouable en multijoueur.",
"version": "1.0.0",
"main": "build/main.js",
"scripts": {
"build": "tsc",
"dev": "ts-node-dev --respawn --transpileOnly ./src/main.ts",
"serve": "tsc && node ./build/main.js"
},
"keywords": [],
"author": "Gerson Sunyé",
"license": "MIT",
"dependencies": {
"@types/express": "^4.16.1",
"body-parser": "^1.18.3",
"ejs": "^2.6.1",
"express": "^4.16.4",
"ts-node": "^8.0.2",
"ts-node-dev": "^1.0.0-pre.32",
"typescript": "^3.3.3"
},
"devDependencies": {
"@stryker-mutator/core": "^1.0.2",
"@stryker-mutator/jasmine-framework": "^1.0.2",
"@stryker-mutator/jasmine-runner": "^1.0.2",
"@stryker-mutator/typescript": "^1.0.2",
"alsatian": "^3.2.1",
"typescript": "^3.7.5"
},
"repository": {
"type": "git",
"url": "git@gitlab.univ-nantes.fr:naomod/idl/projet-2019.git"
}
}
import { Expect, Test } from "alsatian";
import * as isPossible from '../src/move-validation'
import * as pieces from '../src/piece'
import { Chessboard, createEmptyChessboard, putPiece } from '../src/chessboard';
import { Position, position } from '../src/position';
import { Move, move } from '../src/movements';
let chessboard : Chessboard;
const positionA4 : Position = position(0, 3) // A4
const positionA5 : Position = position(0, 4) // A5
const positionA6 : Position = position(0, 5) // A6
const positionA7 : Position = position(0, 6) // A7
const positionA8 : Position = position(0, 7) // A8
const positionB1 : Position = position(1, 0) // B1
const positionB2 : Position = position(1, 1) // B2
const positionB6 : Position = position(1, 5) // B6
const positionC3 : Position = position(2, 1) // C3
const positionC4 : Position = position(2, 3) // C4
const positionC5 : Position = position(2, 4) // C5
const positionC6 : Position = position(2, 5) // C6
const positionC7 : Position = position(2, 6) // C7
const positionD2 : Position = position(3, 1) // D2
const positionD3 : Position = position(3, 2) // D3
const positionD4 : Position = position(3, 3) // D4
const positionD5 : Position = position(3, 4) // D5
const positionD6 : Position = position(3, 5) // D6
const positionE1 : Position = position(4, 0) // E1
const positionE4 : Position = position(4, 3) // E4
const positionE8 : Position = position(4, 7) // E8
const positionF2 : Position = position(5, 1) // F2
const positionF6 : Position = position(5, 5) // F6
const positionG3 : Position = position(6, 2) // G3
const positionG5 : Position = position(6, 4) // G5
const positionH1 : Position = position(7, 0) // H1
const positionH4 : Position = position(7, 3) // H4
const positionH7 : Position = position(7, 8) // H7
// Horizontal moves
const moveE4_H4 : Move = move(positionE4, positionH4);
const moveE4_A4 : Move = move(positionE4, positionA4);
// Vertical moves
const moveE4_E1 : Move = move(positionE4, positionE1);
const moveE4_E8 : Move = move(positionE4, positionE8);
// Diagonal moves
const moveE4_A8 : Move = move(positionE4, positionA8);
const moveE4_B1 : Move = move(positionE4, positionB1);
const moveE4_H7 : Move = move(positionE4, positionH7);
const moveE4_H1 : Move = move(positionE4, positionH1);
// Knight moves
const moveE4_F6 : Move = move(positionE4, positionF6);
const moveE4_G5 : Move = move(positionE4, positionG5);
const moveE4_F2 : Move = move(positionE4, positionF2);
const moveE4_G3 : Move = move(positionE4, positionG3);
const moveE4_D2 : Move = move(positionE4, positionD2);
const moveE4_C3 : Move = move(positionE4, positionC3);
const moveE4_C5 : Move = move(positionE4, positionC5);
const moveE4_D6 : Move = move(positionE4, positionD6);
// Impossible moves
const moveE4_C7 : Move = move(positionE4, positionC7);
const moveE4_B2 : Move = move(positionE4, positionB2);
import { Expect, Test } from "alsatian";
import {parseMoveString,Move} from "../src/movements"
import {Position, equals} from "../src/position"
import * as pieces from './piece'
import {Piece} from './piece'
import {Move} from './movements'
import { Position } from './position';
export function isEmpty(chessboard : Chessboard, position : Position): boolean {
let square: Square = squareAtPosition(chessboard, position);
return square.isEmpty;
}
export function emptyfile(chessboard : Chessboard, move: Move): boolean {
let start: number;
let end : number;
let file : number = move.from!.file;
if (file !== move.to!.file) {
//should not happen
return false;
}
if (move.from!.rank > move.to!.rank) {
start = move.to!.rank;
end = move.from!.rank;
} else {
end = move.to!.rank;
start = move.from!.rank;
}
let i : number = start;
while (i <= end && chessboard.board[file][i].isEmpty) {
i++;
}
return i === end;
}
export interface Square {
position : Position
isEmpty : boolean
piece? : Piece
}
export interface Chessboard {
board : Array<Array<Square>>
nbCoups : number //nombre de coups joués
historique : Array<Move> //historique des coups (optionnel)
}
export function squareAtPosition(chessboard: Chessboard, position : Position): Square {
let square: Square = chessboard.board[position.file][position.rank];
return square;
}
export function pieceAtPosition(chessboard: Chessboard, position : Position): Piece {
let square: Square = squareAtPosition(chessboard, position);
return square.piece!;
}
/** Retourne un échiquier initialisé en début de partie **/
export function createInitialChessboard(): Chessboard {
let chessboard : Chessboard = createChessboard();
// ranks 2 - 6 are empty
for(let rank: number = 2; rank < 6; rank++) {
for(let col: number = 0; col < 8; col++) {
let position: Position = {rank : rank, file : col};
let square : Square = {position : position, isEmpty : true};
chessboard.board[col][rank] = square;
}
}
// Pawns in ranks 2 and 6
let position: Position;
let square : Square;
for(let col: number = 0; col < 8; col++) {
putPieceAtCoordinate(chessboard, col, 1, pieces.whitePawn);
putPieceAtCoordinate(chessboard, col, 6, pieces.blackPawn);
}
// Kings and Queens
putPieceAtCoordinate(chessboard, 4, 0, pieces.whiteKing);
putPieceAtCoordinate(chessboard, 4, 7, pieces.blackKing);
putPieceAtCoordinate(chessboard, 3, 0, pieces.whiteQueen);
putPieceAtCoordinate(chessboard, 3, 7, pieces.blackQueen);
// Bishops
putPieceAtCoordinate(chessboard, 2, 0, pieces.whiteBishop);
putPieceAtCoordinate(chessboard, 2, 7, pieces.blackBishop);
putPieceAtCoordinate(chessboard, 5, 0, pieces.whiteBishop);
putPieceAtCoordinate(chessboard, 5, 7, pieces.blackBishop);
// Knights
putPieceAtCoordinate(chessboard, 1, 0, pieces.whiteKnight);
putPieceAtCoordinate(chessboard, 1, 7, pieces.blackKnight);
putPieceAtCoordinate(chessboard, 6, 0, pieces.whiteKnight);
putPieceAtCoordinate(chessboard, 6, 7, pieces.blackKnight);
// Roocks
putPieceAtCoordinate(chessboard, 0, 0, pieces.whiteRoock);
putPieceAtCoordinate(chessboard, 0, 7, pieces.blackRoock);
putPieceAtCoordinate(chessboard, 7, 0, pieces.whiteRoock);
putPieceAtCoordinate(chessboard, 7, 7, pieces.blackRoock);
return chessboard;
}
export function createEmptyChessboard(): Chessboard {
let newChessboard : Chessboard = createChessboard();
for(let rank: number = 0; rank < 8; rank++) {
for(let col: number = 0; col < 8; col++) {
let position: Position = {rank : rank, file : col};
let square : Square = {position : position, isEmpty : true};
newChessboard.board[col][rank] = square;
}
}
return newChessboard;
}
function createChessboard(): Chessboard {
let board : Square[][] = []
for (let i = 0; i < 8; i++) {
board[i] = [];
}
let newChessboard : Chessboard = {
nbCoups:0,
board:board,
historique:[]
};
return newChessboard;
}
function putPieceAtCoordinate(chessboard: Chessboard, file : number, rank: number, piece : Piece) {
let position : Position = {rank : rank, file : file};
return putPiece(chessboard, position, piece);
}
export function putPiece(chessboard: Chessboard, position: Position, piece : Piece) {
let board : Array<Array<Square>> = chessboard.board;
let square : Square = {position : position, isEmpty : false, piece : piece};
board[position.file][position.rank] = square;
}
\ No newline at end of file
import express = require('express');
import bodyParser = require('body-parser');
const PORT: number = 8080;
const PUBLIC_DIR = 'client'
const app = express();
import { Chessboard, createInitialChessboard } from './chessboard';
import { processMove } from './movements';
class HttpServer {
port: number;
constructor(port: number) {
this.port = port;
}
public onStart(): void {
let chessboard: Chessboard = createInitialChessboard();
let app: express.Application = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(PUBLIC_DIR));
app.set('view engine', 'ejs')
app.listen(this.port, () => {
console.log("Application lancée à l'adresse http://localhost:" + this.port);
});
app.get('/', (req: express.Request, res: express.Response) => {
res.render('index', {error: null});
})
app.get("/status.js", (req: express.Request, res: express.Response) => {
res.end("var boardJSON= " + JSON.stringify(chessboard));
});
app.post("/", (req: express.Request, res: express.Response) => {
let unparsedMove: string = req.body.move;
let didPerfom: boolean = processMove(chessboard, unparsedMove);
let message: string = didPerfom ? "" : "Invalid movement!"
res.render('index', {error: message});
});
}
}
let server: HttpServer = new HttpServer(PORT)
server.onStart();
\ No newline at end of file
import { Chessboard, isEmpty, Square, squareAtPosition } from "./chessboard";
import { Move } from "./movements";
import { equals, left, right, top, bottom } from "./position";
/**
* Checks whether a Black Pawn can perform a given move.
* A pawn can move forward to the unoccupied square immediately in front of
* it on the same file, or on its first move it can advance two squares along
* the same file, provided both squares are unoccupied (black dots in the
* diagram); or the pawn can capture an opponent's piece on a square diagonally
* in front of it on an adjacent file, by moving to that square (black "x"s).
*
* A pawn has two special moves: the en passant capture and promotion.
*
* @param board The chessboard of the current game
* @param move
*/
export function blackPawnMove(board: Chessboard, move: Move): boolean {
// #TODO: Manage special 'En passant' move.
if (equals(move.to!, bottom(move.from!))) {
//console.log("Single forward");
return isEmpty(board, move.to!);
}
if (move.from!.rank === 6 && equals(move.to!, bottom(bottom(move.from!)))) {
//console.log("Double forward");
return isEmpty(board, bottom(move.from!)) && isEmpty(board, move.to!);
}
if (equals(move.to!, left(bottom(move.from!))) || equals(move.to!, right(bottom(move.from!)))) {
let destination: Square = squareAtPosition(board, move.to!);
return !(destination.isEmpty || !destination.piece!.isWhite)
}