Commit 0271621a authored by Gerson Sunyé's avatar Gerson Sunyé
Browse files

Code improvements: new unit tests, new interfaces, new validation functions

parent 8796c7a5
......@@ -16,23 +16,23 @@
<h1>Online Chess</h1>
<!-- L'échiquier est un tableau de 8 lignes x 8 colonnes -->
<table class="echiquier">
<tr><th></th><th>a</th><th>b</th><th>c</th><th>d</th><th>e</th><th>f</th><th>g</th><th>f</th></tr>
<tr><th>1</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>1</th></tr>
<tr><th>2</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>2</th></tr>
<tr><th>3</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>3</th></tr>
<tr><th>4</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>4</th></tr>
<tr><th>5</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>5</th></tr>
<tr><th>6</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>6</th></tr>
<tr><th>7</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>7</th></tr>
<table class="chessboard">
<tr><th></th><th>a</th><th>b</th><th>c</th><th>d</th><th>e</th><th>f</th><th>g</th><th>h</th></tr>
<tr><th>8</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>8</th></tr>
<tr><th></th><th>a</th><th>b</th><th>c</th><th>d</th><th>e</th><th>f</th><th>g</th><th>f</th></tr>
<tr><th>7</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>7</th></tr>
<tr><th>6</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>6</th></tr>
<tr><th>5</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>5</th></tr>
<tr><th>4</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>4</th></tr>
<tr><th>3</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>3</th></tr>
<tr><th>2</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>2</th></tr>
<tr><th>1</th><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><th>1</th></tr>
<tr><th></th><th>a</th><th>b</th><th>c</th><th>d</th><th>e</th><th>f</th><th>g</th><th>h</th></tr>
</table><br/>
<!-- Formulaire de saisie et emission du coup à jouer
Envoie une requêtes sur '/ (POST)' au serveur -->
<form method="POST">
<input type="text" name="coup" placeholder="Coup à jouer (TaH3)" autofocus></input>
<input type="submit" value="Envoyer"></input>
<input type="text" name="coup" placeholder="Movement (d7-d6)" autofocus></input>
<input type="submit" value="Send"></input>
</form>
</center>
</body>
......
......@@ -10,22 +10,17 @@
* Initialise l'échiquier en fonction de la variable "echiquier"
*/
function init(){
const CHESSBOARD_LENGTH = 8;
/************** PARTIE ELEVE 3 ****************/
/******* A ADAPTER SELON VOS INTERFACES *******/
for(var i=0;i<echiquier.plateau.length;i++){
for(var j=0;j<echiquier.plateau.length;j++){
var piece = echiquier.plateau[i][j];
if(piece.nom=="vide"){
//Vidage de la case
document.getElementsByTagName("tr")[j+1].getElementsByTagName("td")[i].innerHTML="";
}else{
//Affichage du symbole / image
document.getElementsByTagName("tr")[j+1].getElementsByTagName("td")[i].innerHTML=piece.symbole; // version symbole
//document.getElementsByTagName("tr")[j+1].getElementsByTagName("td")[i].innerHTML="<img src=\""+piece.image+"\"/>"; //version image
for(var col = 0; col < CHESSBOARD_LENGTH; col++){
for(var line = 0; line < CHESSBOARD_LENGTH; line++){
var square = boardJSON.board[col][7-line]; // HTML Table lines are inversed
var squareView = document.getElementsByTagName("tr")[line+1].getElementsByTagName("td")[col];
if(square.isEmpty) {
squareView.innerHTML = "";
} else {
squareView.innerHTML = square.piece.symbol;
}
}
}
}
}
\ No newline at end of file
.echiquier {
.chessboard {
border:brown 10px solid;border-collapse: collapse;
}
/* Mise en forme des noms de ligne/colonne */
.echiquier th{
.chessboard th{
padding:5px;
}
/* Style des cases de l'echiquier */
.echiquier td{
.chessboard td{
/*Dimensionnement relatif au navigateur*/
width:8vmin;
height:8vmin;
......@@ -24,12 +24,12 @@
/* Gestion des cases blanches */
.echiquier tr:nth-child(odd) td:nth-child(even), .echiquier tr:nth-child(even) td:nth-child(odd) {
.chessboard tr:nth-child(even) td:nth-child(even), .chessboard tr:nth-child(odd) td:nth-child(odd) {
background: bisque;
}
/* Gestion des images dans les cases de l'échiquier (option) */
.echiquier td img{
.chessboard td img{
max-width:90%;
max-height:90%;
}
\ No newline at end of file
{
"name": "projetEchecs",
"description": "",
"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"
"build": "tsc",
"test": "ts-node node_modules/jasmine/bin/jasmine",
"dev": "ts-node-dev --respawn --transpileOnly ./src/main.ts",
"serve": "tsc && node ./build/main.js"
},
"keywords": [],
"author": "Quentin Tonneau",
"license": "MIT",
"dependencies": {
"body-parser": "^1.18.2",
"express": "^4.16.3"
"body-parser": "^1.18.3",
"express": "^4.16.4",
"ts-node": "^8.0.2",
"ts-node-dev": "^1.0.0-pre.32",
"typescript": "^3.3.3"
},
"devDependencies": {
"@types/jasmine": "^3.3.9",
"jasmine": "^3.3.1",
"jasmine-core": "^3.3.0",
"jasmine-ts": "^0.3.0"
},
"repository": {
"type": "git",
"url": "git@gitlab.univ-nantes.fr:naomod/software-development-course/onlineChess.git"
}
}
/*
* Programme principal du serveur d'échecs en ligne
* @Author: Quentin Tonneau
* @Date: 2018-03-15 22:23:16
* @Last Modified by: Quentin Tonneau
* @Last Modified time: 2018-03-19 11:45:29
*/
//Imports des modules serveur web
import express = require('express');
import bodyParser = require('body-parser');
declare var __dirname;
const _PORT_ = 8080; //port d'écoute du serveur
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(__dirname+'/../client')); //distribution automatique des fichiers (ex : index.html)
/************** PARTIE ELEVE 1 ****************/
import { Echiquier, createEchiquier } from './echiquier';
import { jouerCoup } from './Coup';
let echiquier:Echiquier = createEchiquier(); //Création d'une variable globale de la partie en cours
/************* FIN PARTIE ELEVE 1 ************/
//Fonction de traitement d'un coup envoyé par le navigateur
app.post("/",function(req,res){
let coup : string = req.body.coup; //récupération du coup
/************** PARTIE ELEVE 2 ****************/
echiquier = jouerCoup(coup,echiquier);
/************* FIN PARTIE ELEVE 2 ************/
res.redirect("/");//On redirige l'utilisateur vers l'affichage du nouvel echiquier
});
//Fonction retournant l'échiquier actuel
app.get("/status.js",function(req,res){
res.end("var echiquier = "+JSON.stringify(echiquier));
});
//Lancement de l'application
app.listen(_PORT_, function () {
console.log('Application lancée à l\'adresse http://localhost:'+_PORT_);
});
\ No newline at end of file
import {parseMoveString,Move} from "../src/movements"
import {Position, equals} from "../src/position"
describe("parseMoveString", () => {
it("A2-A4", () => {
let move: Move = parseMoveString("A2-A4");
let expectedFrom: Position = {column: 0, line:1}
let expectedTo: Position = {column:0, line:3}
expect(move.isValid).toBeTruthy();
expect(equals(expectedFrom, move.from)).toBeTruthy();
expect(equals(expectedTo, move.to)).toBeTruthy();
});
it("B8-B3", () => {
let move: Move = parseMoveString("B8-B3");
let expectedFrom: Position = {column: 1, line:7}
let expectedTo: Position = {column:1, line:2}
expect(move.isValid).toBeTruthy();
expect(equals(expectedFrom, move.from)).toBeTruthy();
expect(equals(expectedTo, move.to)).toBeTruthy();
});
it("H8-H3", () => {
let move: Move = parseMoveString("H8-H3");
let expectedFrom: Position = {column: 7, line:7}
let expectedTo: Position = {column:7, line:2}
expect(move.isValid).toBeTruthy();
expect(equals(expectedFrom, move.from)).toBeTruthy();
expect(equals(expectedTo, move.to)).toBeTruthy();
});
it("a1-h8 == A1-H8", () => {
let lowercaseMove: Move = parseMoveString("a1-h8");
let uppercaseMove: Move = parseMoveString("A1-H8");
expect(lowercaseMove.isValid).toBeTruthy();
expect(uppercaseMove.isValid).toBeTruthy();
expect(equals(lowercaseMove.from, uppercaseMove.from)).toBeTruthy();
expect(equals(lowercaseMove.to, uppercaseMove.to)).toBeTruthy();
});
})
\ No newline at end of file
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.ts"
],
"helpers": [
"helpers/**/*.js"
],
"stopSpecOnExpectationFailure": false,
"random": true
}
/*
* Module d'échiquier
* @Author: Quentin Tonneau
* @Date: 2018-03-15 22:15:13
* @Last Modified by: Quentin Tonneau
* @Last Modified time: 2018-03-15 22:16:12
*/
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 emptyColumn(chessboard : Chessboard, move: Move): boolean {
let start: number;
let end : number;
let column : number = move.from.column;
if (column !== move.to.column) {
//should not happen
return false;
}
if (move.from.line > move.to.line) {
start = move.to.line;
end = move.from.line;
} else {
end = move.to.line;
start = move.from.line;
}
let i : number = start;
while (i <= end && chessboard.board[column][i].isEmpty) {
i++;
}
return i === end;
}
export interface Square {
position : Position
isEmpty : boolean
piece? : Piece
}
export interface Chessboard {
board : Array<Array<Square>> //plateau de jeu
nbCoups : number //nombre de coups joués
historique : Array<Move> //historique des coups (optionnel)
}
export function squareAtPosition(chessboard: Chessboard, position : Position): Square {
console.log(JSON.stringify(position));
let square: Square = chessboard.board[position.column][position.line];
console.log(JSON.stringify(square));
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 createChessboard(): Chessboard {
let board : Square[][] = []
for (let i = 0; i < 8; i++) {
board[i] = [];
}
// Lines 2 - 6 are empty
for(let line: number = 2; line < 6; line++) {
for(let col: number = 0; col < 8; col++) {
let position: Position = {line : line, column : col};
let square : Square = {position : position, isEmpty : true};
board[col][line] = square;
}
}
// Pawns in lines 2 and 6
let position: Position;
let square : Square;
for(let col: number = 0; col < 8; col++) {
putSquare(board, 1, col, pieces.whitePawn);
putSquare(board, 6, col, pieces.blackPawn);
}
// Kings and Queens
putSquare(board, 0, 3, pieces.whiteKing);
putSquare(board, 7, 3, pieces.blackKing);
putSquare(board, 0, 4, pieces.whiteQueen);
putSquare(board, 7, 4, pieces.blackQueen);
// Bishops
putSquare(board, 0, 2, pieces.whiteBishop);
putSquare(board, 7, 2, pieces.blackBishop);
putSquare(board, 0, 5, pieces.whiteBishop);
putSquare(board, 7, 5, pieces.blackBishop);
// Knights
putSquare(board, 0, 1, pieces.whiteKnight);
putSquare(board, 7, 1, pieces.blackKnight);
putSquare(board, 0, 6, pieces.whiteKnight);
putSquare(board, 7, 6, pieces.blackKnight);
// Roocks
putSquare(board, 0, 0, pieces.whiteRoock);
putSquare(board, 7, 0, pieces.blackRoock);
putSquare(board, 0, 7, pieces.whiteRoock);
putSquare(board, 7, 7, pieces.blackRoock);
let newChessboard : Chessboard = {
nbCoups:0,
board:board,
historique:[]
};
return newChessboard;
}
function putSquare(board : Array<Array<Square>>, line: number, column : number, piece : Piece) {
let position : Position = {line : line, column : column};
let square : Square = {position : position, isEmpty : false, piece : piece};
board[column][line] = square;
}
import express = require('express');
import bodyParser = require('body-parser');
const PORT: number = 8080;
const PUBLIC_DIR = 'client'
const app = express();
import { Chessboard, createChessboard } from './chessboard';
import { processMove } from './movements';
class HttpServer {
port : number;
constructor(port: number) {
this.port = port;
}
public onStart(): void {
let chessboard: Chessboard = createChessboard();
let app: express.Application = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(PUBLIC_DIR));
app.listen(this.port, () => {
console.log('Application lancée à l\'adresse http://localhost:' + this.port);
});
app.get("/status.js", (req, res) => {
res.end("var boardJSON= "+JSON.stringify(chessboard));
});
app.post("/", (req, res) => {
let coup : string = req.body.coup;
chessboard = processMove(chessboard, coup);
res.redirect("/");
});
}
}
let server: HttpServer = new HttpServer(PORT)
server.onStart();
\ No newline at end of file
import { Chessboard, squareAtPosition, Square, pieceAtPosition, isEmpty } from './chessboard'
import { Position, top, bottom, right, left, equals } from "./position";
import * as pieces from './piece'
import {Piece} from './piece'
const VALID_MOVE_STRING: RegExp = new RegExp('([a-z]|[A-Z])([1-8])-([A-H]|[a-z])([1-8])')
export interface Move {
isValid : boolean;
from? : Position;
to? : Position;
}
export function processMove(chessboard:Chessboard, movementString: string): Chessboard {
let move : Move = parseMoveString(movementString);
console.log("Move: " + move.from.column + move.from.line +
move.to.column + move.to.line);
if (move.isValid && isMovePossible(chessboard, move)) {
performMove(chessboard, move);
} else {
console.log("Invalid movement !");
}
return chessboard;
}
export function parseMoveString(movementString: string): Move {
let newMove : Move;
if (movementString.length != 5 || ! movementString.match(VALID_MOVE_STRING)) {
let invalidMove : Move = {isValid : false};
newMove = invalidMove;
} else {
let fromColumn : number = movementString.charCodeAt(0);
let fromLine : number = parseInt(movementString[1]);
let toColumn : number = movementString.charCodeAt(3);
let toLine : number = parseInt(movementString[4]);
// In Unicode, charCode('A') == 65, charCode('a') == 97
// Remember that Arrays start from [0][0] == position 'A1'
let from : Position = {line : fromLine -1, column: fromColumn > 90 ? fromColumn - 97 : fromColumn - 65}
let to : Position = {line : toLine -1, column: toColumn > 90 ? toColumn - 97 : toColumn - 65 }
newMove = {isValid: true, from: from, to: to}
}
return newMove;
}
function isMovePossible(chessboard : Chessboard, move : Move): boolean {
let square: Square = squareAtPosition(chessboard, move.from);
if (square.isEmpty) {
console.log("Empty from");
return false;
}
let piece : Piece = square.piece;
switch(piece) {
case pieces.whitePawn : return isValidWhitePawnMove(chessboard, move);
case pieces.blackPawn : {
console.log("Black pawn");
return isValidBlackPawnMove(chessboard, move);
}
}
return true;
}
function performMove(board : Chessboard, move : Move) {
let source : Square = squareAtPosition(board, move.from);
let destination : Square = squareAtPosition(board, move.to);
destination.piece = source.piece;
destination.isEmpty = false;
source.isEmpty = true;
}
function isValidBlackPawnMove(board : Chessboard, move : Move) {
// #TODO: Manage special 'En passant' move.
if (equals(move.to, top(move.from))) {
console.log("Single forward");
return isEmpty(board, move.to);
}
if (move.from.line === 6 && equals(move.to, top(top(move.from)))) {
console.log("Double forward");
return isEmpty(board, top(move.from)) && isEmpty(board, move.to);
}
if (equals(move.to, left(top(move.from))) || equals(move.to, right(top(move.from)))) {
let destination: Square = squareAtPosition(board, move.to);
return (!destination.isEmpty && !destination.piece.isWhite)
}
return false;
}
function isValidWhitePawnMove(board : Chessboard, move : Move) {
// #TODO: Manage special 'En passant' move.
if (equals(move.to,bottom(move.from))) {
return isEmpty(board, move.to);
}
if (move.from.line === 1 && equals(move.to, bottom(bottom(move.from)))) {
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)
}
return false;
}
function isSameColumn(move: Move) {
return move.from.column === move.to.column;
}
function height(move : Move) : number {
return Math.abs(move.from.column - move.to.column);
}
export interface Piece {
symbol : string,
isWhite : boolean,
name : string,
}
export const whitePawn : Piece = {symbol : "", name: "White Pawn", isWhite : true};
export const whiteKing : Piece = {symbol : "", name: "White Pawn", isWhite : true};
export const whiteQueen : Piece = {symbol : "", name: "White Pawn", isWhite : true};
export const whiteRoock : Piece = {symbol : "", name: "White Pawn", isWhite : true};
export const whiteKnight : Piece = {symbol : "", name: "White Pawn", isWhite : true};
export const whiteBishop : Piece = {symbol : "", name: "White Pawn", isWhite : true};
export const blackPawn : Piece = {symbol : "", name: "Black Pawn", isWhite : false};
export const blackKing : Piece = {symbol : "", name: "Black Pawn", isWhite : false};
export const blackQueen : Piece = {symbol : "", name: "Black Pawn", isWhite : false};
export const blackRoock : Piece = {symbol : "", name: "Black Pawn", isWhite : false};
export const blackKnight : Piece = {symbol : "", name: "Black Pawn", isWhite : false};
export const blackBishop : Piece = {symbol : "", name: "Black Pawn", isWhite : false};
\ No newline at end of file
export interface Position {
line: number;
column: number;
}
export function top(pos: Position) : Position {
let topPosition = {column: pos.column, line: pos.line - 1};
return topPosition;
}
export function bottom(pos: Position) : Position {
let topPosition = {column: pos.column, line: pos.line + 1};
return topPosition;
}
export function left(pos: Position) : Position {
let leftPosition = {column: pos.column - 1, line: pos.line};
return leftPosition;
}
export function right(pos: Position) : Position {
let rightPosition = {column: pos.column + 1, line: pos.line};
return rightPosition;