Professional Documents
Culture Documents
1. Introduction
The current level of development in computer chess programming is fairly complicated, yet
interesting as well. In this project, we were supposed to develop a chess-playing program. The
program was supposed to play chess at a good level.
2
There can be two-player games which are not of perfect information such as Scrabble
because the opponent's move cannot be predicted.
Minimax is a kind of backtracking algorithm that is used in decision making and game theory to
find the optimal move for a player, assuming that your opponent also plays optimally. It is widely
used in two player turn based games such as Tic-Tac-Toe, Backgamon, Mancala, Chess, etc.
In Minimax the two players are called maximizer and minimizer. The maximizer tries to get the
highest score possible while the minimizer tries to get the lowest score possible while minimizer
tries to do opposite.
Every board state has a value associated with it. In a given state if the maximizer has upper hand
then, the score of the board will tend to be some positive value. If the minimizer has the upper
hand in that board state then it will tend to be some negative value. The values of the board are
calculated by some heuristics which are unique for every type of game.
2. Problem Statement
A programmatic approach to play the game of Chess using Min-Max AI algorithms.
3. Literature Review
The program is implemented entirely in Java. The following diagram shows the relationships
between different modules of the program. Below the diagram is a listing of the modules, as they
correspond to Java classes, with short descriptions and links to the source files. The algorithms
used are : 1. Min-max Searching 2. Alpha beta pruning
Game
ECOReader Move
The evaluation function calculates a numeric value for each node of the game tree, we need to
apply a certain search strategy to construct an acceptable chess program. At this search step, we
are planning to apply minimax search algorithm to choose the best possible next move and
alpha-beta pruning technique to increase the performance by decreasing the search space.
3
The basic idea underlying all two-agent search algorithms is Minimax method. Minimax is a
recursive algorithm used for choosing the next move in a game. A tree of legal moves is built
and played. Each move is evaluated using an evaluation function. The computer makes the move
that maximizes the minimum value of the position resulting from the opponent's possible
following moves. The details of our Minimax algorithm will be explained later. The basic idea
behind the Alpha-Beta Pruning algorithm is that, once you have a good move, you can quickly
eliminate alternatives that lead to disaster. We know that there are many of those quickly
disposable moves in chess game. The alpha-beta pruning function is actually an improvement of
the Minimax search method. It reduces the number of tree nodes to evaluate by eliminating a
move when at least one possibility was proved worse than a previously evaluated one. The
details of our alpha-beta pruning algorithm will be explained later.
This project implements a classic version of Chess. The Chess game follows the basic rules of
chess, and all the chess pieces only move according to valid moves for that piece. Our
implementation of Chess is for One player against Artificial Intelligence as the 2nd player. It is
played on an 8x8 checkered board , with a dark square in each player’s lower left corner.
Research has been conducted in Chess artificial intelligence and creating a more Realistic and
moreover intelligent Chess match. Russell discussed applying an evaluation function to the
leaves of the tree, that judges the value of certain moves from a given position. Another method
he mentioned is to cut off the search by setting a limit to its depth. Russell evaluated a particular
technique called alpha-beta pruning to remove branches of a tree that will not influence the final
decision. Bratko discusses the method of chess players to ”chunk” together familiar chess
patterns, and using this to reduce the complexity for AI when considering a position. However,
this technique is in its early stages, and requires that multiple assumptions and
a complicated detection process. Berliner recognized that two similar positions can be very
different. However, this kind of research is essentially never complete.
This paper concerns the research and development of an Automated Chess Annotation program -
a program which attempts to provide automatic commentary on any chess game it is given. The
closest research to chess annotation is that performed by de Groot in 1965, who performed
psychological experiments to try to determine how humans thought about concepts in chess, but
the interaction between computer chess and human commentary does not appear to have been
examined before. Development in computer programs has been dominated by number crunching
evaluation over pattern matching, and this has lead development away from human thought
patterns and annotation. The program is developed by extending an existing open source chess
program called Crafty. Crafty was chosen because it had several attractive features with regards
to chess annotation development, especially regarding the free availability of its source code and
its strong chess-playing ability. Crafty was, however, found over the course of the project to have
several disadvantages with regards to development, in particular concerning the language it is
written in and the complexity and interlinking of its code. Chess Annotation is the process of
providing commentary on a chess game. Computers play chess by attempting to `brute-force'
examine as many potential moves in as short a time as possible, using a limited pattern matching
system and a lot of number crunching to evaluate each move.
4
Automated annotation must therefore convert the computer's analysis into the concepts a human
uses when playing chess. This may take the form of simply putting the computer's values into
words (as in “White's last move was very poor”), deriving human-recognizable features from the
computer's data (such as threats), or even creating new measures to deal with abstract human
concepts (such as initiative, psychological advantage, and obviousness).The value of this work is
in the insights it provides into the realm of human and machine perception.
5. Design
5
5.1 Pseudo code
Step 1: Move generation and board visualization
We’ll use the chess.js library for move generation, and chessboard. js for visualizing the board.
The move generation library basically implements all the rules of chess. Based on this, we can
calculate all legal moves for a given board state. Using these libraries will help us focus only on
the most interesting task ,creating the algorithm that finds the best move.
Function that just returns a random move from all of the possible moves:
return newGameMoves[Math.floor(Math.random() *
newGameMoves.length)];
With the evaluation function, we’re able to create an algorithm that chooses the move that gives
the highest evaluation
var calculateBestMove = function (game)
6
{
var newGameMoves = game.ugly_moves();
var bestMove = null;
//use any negative large number
var bestValue = -9999;
for (var i = 0; i < newGameMoves.length; i++) {
var newGameMove = newGameMoves[i];
game.ugly_move(newGameMove);
//take the negative as AI plays as black
var boardValue = -evaluateBoard(game.board())
game.undo();
if (boardValue > bestValue) {
bestValue = boardValue;
bestMove = newGameMove
}
}
return bestMove;
};
7
Step 3: Search tree using Minimax
Next we’re going to create a search tree from which the algorithm can chose the best move. This
is done by using the Minimax algorithm. In this algorithm, the recursive tree of all possible moves
is explored to a given depth, and the position is evaluated at the ending “leaves” of the tree. After
that, we return either the smallest or the largest value of the child to the parent node, depending on
whether it’s a white or black to move. (That is, we try to either minimize or maximize the
outcome at each level.)
if (depth === 0) {
return -evaluateBoard(game.board());
}
var newGameMoves = game.ugly_moves();
if (isMaximisingPlayer)
{
var bestMove = -9999;
for (var i = 0; i < newGameMoves.length; i++)
{
game.ugly_move(newGameMoves[i]);
bestMove = Math.max(bestMove, minimax(depth - 1,game,!isMaximisingPlayer));
game.undo();
}
return bestMove;
}
else
{
var bestMove = 9999;
for (var i = 0; i < newGameMoves.length; i++)
{
game.ugly_move(newGameMoves[i]);
bestMove = Math.min(bestMove, minimax(depth - 1, game, !isMaximisingPlayer));
game.undo();
}
return bestMove;
}
};
8
Step 4: Alpha-beta pruning
Alpha-beta pruning is an optimization method to the minimax algorithm that allows us to
disregard some branches in the search tree. This helps us evaluate the minimax search tree much
deeper, while using the same resources.
The alpha-beta pruning is based on the situation where we can stop evaluating a part of the search
tree if we find a move that leads to a worse situation than a previously discovered move.
9
5.2 Flow chart
10
State space tree :
Flow chart:
11
START
Position evaluation.
Possibility of move.
NO YE
S
Evaluate best
possibility.
Minimax (sometimes MinMax or MM) is a decision rule used in decision theory, game
END
theory, statistics and philosophy for minimizing the possible loss for a worst case (maximum
loss) scenario. When dealing with gains, it is referred to as "maximin"—to maximize the
minimum gain. Originally formulated for two-player zero-sum game theory, covering both the
cases where players take alternate moves and those where they make simultaneous moves, it has
also been extended to more complex games and to general decision-making in the presence of
uncertainty.
12
6. Implementation
File: SCRIPT.js
var board,
game = new Chess();
if (isMaximisingPlayer) {
var bestMove = -9999;
13
for (var i = 0; i < newGameMoves.length; i++) {
game.ugly_move(newGameMoves[i]);
bestMove = Math.max(bestMove, minimax(depth - 1, game, alpha,
beta, !isMaximisingPlayer));
game.undo();
alpha = Math.max(alpha, bestMove);
if (beta <= alpha) {
return bestMove;
}
}
return bestMove;
} else {
var bestMove = 9999;
for (var i = 0; i < newGameMoves.length; i++) {
game.ugly_move(newGameMoves[i]);
bestMove = Math.min(bestMove, minimax(depth - 1, game, alpha,
beta, !isMaximisingPlayer));
game.undo();
beta = Math.min(beta, bestMove);
if (beta <= alpha) {
return bestMove;
}
}
return bestMove;
}
};
14
var pawnEvalWhite =
[
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0],
[1.0, 1.0, 2.0, 3.0, 3.0, 2.0, 1.0, 1.0],
[0.5, 0.5, 1.0, 2.5, 2.5, 1.0, 0.5, 0.5],
[0.0, 0.0, 0.0, 2.0, 2.0, 0.0, 0.0, 0.0],
[0.5, -0.5, -1.0, 0.0, 0.0, -1.0, -0.5, 0.5],
[0.5, 1.0, 1.0, -2.0, -2.0, 1.0, 1.0, 0.5],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
];
var knightEval =
[
[-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0],
[-4.0, -2.0, 0.0, 0.0, 0.0, 0.0, -2.0, -4.0],
[-3.0, 0.0, 1.0, 1.5, 1.5, 1.0, 0.0, -3.0],
[-3.0, 0.5, 1.5, 2.0, 2.0, 1.5, 0.5, -3.0],
[-3.0, 0.0, 1.5, 2.0, 2.0, 1.5, 0.0, -3.0],
[-3.0, 0.5, 1.0, 1.5, 1.5, 1.0, 0.5, -3.0],
[-4.0, -2.0, 0.0, 0.5, 0.5, 0.0, -2.0, -4.0],
[-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0]
];
var bishopEvalWhite = [
[ -2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0],
[ -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0],
[ -1.0, 0.0, 0.5, 1.0, 1.0, 0.5, 0.0, -1.0],
[ -1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, -1.0],
[ -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, -1.0],
[ -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0],
[ -1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, -1.0],
[ -2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0]
];
15
var rookEvalWhite = [
[ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[ 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5],
[ -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
[ -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
[ -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
[ -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
[ -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
[ 0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0]
];
var evalQueen = [
[ -2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0],
[ -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0],
[ -1.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0],
[ -0.5, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5],
[ 0.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5],
[ -1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0],
[ -1.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, -1.0],
[ -2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0]
];
var kingEvalWhite = [
16
var getPieceValue = function (piece, x, y) {
if (piece === null) {
return 0;
}
var getAbsoluteValue = function (piece, isWhite, x ,y) {
if (piece.type === 'p') {
return 10 + ( isWhite ? pawnEvalWhite[y][x] : pawnEvalBlack[y][x]
);
} else if (piece.type === 'r') {
return 50 + ( isWhite ? rookEvalWhite[y][x] : rookEvalBlack[y]
[x] );
} else if (piece.type === 'n') {
return 30 + knightEval[y][x];
} else if (piece.type === 'b') {
return 30 + ( isWhite ? bishopEvalWhite[y][x] : bishopEvalBlack[y]
[x] );
} else if (piece.type === 'q') {
return 90 + evalQueen[y][x];
} else if (piece.type === 'k') {
return 900 + ( isWhite ? kingEvalWhite[y][x] : kingEvalBlack[y]
[x] );
}
throw "Unknown piece type: " + piece.type;
};
17
var makeBestMove = function () {
var bestMove = getBestMove(game);
game.ugly_move(bestMove);
board.position(game.fen());
renderMoveHistory(game.history());
if (game.game_over()) {
alert('Game over');
}
};
var positionCount;
var getBestMove = function (game) {
if (game.game_over()) {
alert('Game over');
}
positionCount = 0;
var depth = parseInt($('#search-depth').find(':selected').text());
$('#position-count').text(positionCount);
$('#time').text(moveTime/1000 + 's');
$('#positions-per-s').text(positionsPerS);
return bestMove;
};
18
};
removeGreySquares();
if (move === null) {
return 'snapback';
}
renderMoveHistory(game.history());
window.setTimeout(makeBestMove, 250);
};
greySquare(square);
19
removeGreySquares();
};
squareEl.css('background', background);
};
var cfg = {
draggable: true,
position: 'start',
onDragStart: onDragStart,
onDrop: onDrop,
onMouseoutSquare: onMouseoutSquare,
onMouseoverSquare: onMouseoverSquare,
onSnapEnd: onSnapEnd
};
board = ChessBoard('board', cfg);
File: INDEX.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="lib/chessboardjs/css/chessboard-0.3.0.css">
20
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="board" class="board"></div>
<div class="info">
Search depth:
<select id="search-depth">
<option value="1">1</option>
<option value="2">2</option>
<option value="3" selected>3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
<br>
<span>Positions evaluated: <span id="position-count"></span></span>
<br>
<span>Time: <span id="time"></span></span>
<br>
<span>Positions/s: <span id="positions-per-s"></span> </span>
<br>
<br>
<div id="move-history" class="move-history">
</div>
</div>
<script src="lib/jquery/jquery-3.2.1.min.js"></script>
<script src="lib/chessboardjs/js/chess.js"></script>
<script src="lib/chessboardjs/js/chessboard-0.3.0.js"></script>
21
<script src="script.js"></script>
<script>
</script>
</body>
</html>
File: package.json
{
"name": "new-chess-ai",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "http-server ./"
},
"author": "",
"license": "ISC",
"dependencies": {
"http-server": "^0.9.0"
}
}
File: Chess.js
/*
* Copyright (c) 2017, Jeff Hlywa (jhlywa@gmail.com)
* All rights reserved.
22
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
notice,
* this list of conditions and the following disclaimer in the
documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*----------------------------------------------------------------------------*/
/* @license
23
* Copyright (c) 2017, Jeff Hlywa (jhlywa@gmail.com)
* Released under the BSD license
* https://github.com/jhlywa/chess.js/blob/master/LICENSE
*/
var DEFAULT_POSITION =
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
var PAWN_OFFSETS = {
b: [16, 32, 17, 15],
w: [-16, -32, -17, -15]
};
var PIECE_OFFSETS = {
n: [-18, -33, -31, -14, 18, 33, 31, 14],
b: [-17, -15, 17, 15],
r: [-16, 1, 16, -1],
q: [-17, -16, -15, 1, 17, 16, 15, -1],
k: [-17, -16, -15, 1, 17, 16, 15, -1]
};
24
var ATTACKS = [
20, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0,20, 0,
0,20, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0,20, 0, 0,
0, 0,20, 0, 0, 0, 0, 24, 0, 0, 0, 0,20, 0, 0, 0,
0, 0, 0,20, 0, 0, 0, 24, 0, 0, 0,20, 0, 0, 0, 0,
0, 0, 0, 0,20, 0, 0, 24, 0, 0,20, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,20, 2, 24, 2,20, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
24,24,24,24,24,24,56, 0, 56,24,24,24,24,24,24, 0,
0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,20, 2, 24, 2,20, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,20, 0, 0, 24, 0, 0,20, 0, 0, 0, 0, 0,
0, 0, 0,20, 0, 0, 0, 24, 0, 0, 0,20, 0, 0, 0, 0,
0, 0,20, 0, 0, 0, 0, 24, 0, 0, 0, 0,20, 0, 0, 0,
0,20, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0,20, 0, 0,
20, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0,20
];
var RAYS = [
17, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 15, 0,
0, 17, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 15, 0, 0,
0, 0, 17, 0, 0, 0, 0, 16, 0, 0, 0, 0, 15, 0, 0, 0,
0, 0, 0, 17, 0, 0, 0, 16, 0, 0, 0, 15, 0, 0, 0, 0,
0, 0, 0, 0, 17, 0, 0, 16, 0, 0, 15, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 17, 0, 16, 0, 15, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 17, 16, 15, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0, -1, -1, -1,-1, -1, -1, -1, 0,
0, 0, 0, 0, 0, 0,-15,-16,-17, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,-15, 0,-16, 0,-17, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,-15, 0, 0,-16, 0, 0,-17, 0, 0, 0, 0, 0,
0, 0, 0,-15, 0, 0, 0,-16, 0, 0, 0,-17, 0, 0, 0, 0,
0, 0,-15, 0, 0, 0, 0,-16, 0, 0, 0, 0,-17, 0, 0, 0,
0,-15, 0, 0, 0, 0, 0,-16, 0, 0, 0, 0, 0,-17, 0, 0,
-15, 0, 0, 0, 0, 0, 0,-16, 0, 0, 0, 0, 0, 0,-17
];
var SHIFTS = { p: 0, n: 1, b: 2, r: 3, q: 4, k: 5 };
var FLAGS = {
25
NORMAL: 'n',
CAPTURE: 'c',
BIG_PAWN: 'b',
EP_CAPTURE: 'e',
PROMOTION: 'p',
KSIDE_CASTLE: 'k',
QSIDE_CASTLE: 'q'
};
var BITS = {
NORMAL: 1,
CAPTURE: 2,
BIG_PAWN: 4,
EP_CAPTURE: 8,
PROMOTION: 16,
KSIDE_CASTLE: 32,
QSIDE_CASTLE: 64
};
var RANK_1 = 7;
var RANK_2 = 6;
var RANK_3 = 5;
var RANK_4 = 4;
var RANK_5 = 3;
var RANK_6 = 2;
var RANK_7 = 1;
var RANK_8 = 0;
var SQUARES = {
a8: 0, b8: 1, c8: 2, d8: 3, e8: 4, f8: 5, g8: 6, h8: 7,
a7: 16, b7: 17, c7: 18, d7: 19, e7: 20, f7: 21, g7: 22, h7: 23,
a6: 32, b6: 33, c6: 34, d6: 35, e6: 36, f6: 37, g6: 38, h6: 39,
a5: 48, b5: 49, c5: 50, d5: 51, e5: 52, f5: 53, g5: 54, h5: 55,
a4: 64, b4: 65, c4: 66, d4: 67, e4: 68, f4: 69, g4: 70, h4: 71,
a3: 80, b3: 81, c3: 82, d3: 83, e3: 84, f3: 85, g3: 86, h3: 87,
a2: 96, b2: 97, c2: 98, d2: 99, e2: 100, f2: 101, g2: 102, h2: 103,
a1: 112, b1: 113, c1: 114, d1: 115, e1: 116, f1: 117, g1: 118, h1: 119
};
var ROOKS = {
26
w: [{square: SQUARES.a1, flag: BITS.QSIDE_CASTLE},
{square: SQUARES.h1, flag: BITS.KSIDE_CASTLE}],
b: [{square: SQUARES.a8, flag: BITS.QSIDE_CASTLE},
{square: SQUARES.h8, flag: BITS.KSIDE_CASTLE}]
};
function clear() {
board = new Array(128);
kings = {w: EMPTY, b: EMPTY};
turn = WHITE;
castling = {w: 0, b: 0};
ep_square = EMPTY;
half_moves = 0;
move_number = 1;
history = [];
header = {};
update_setup(generate_fen());
}
function reset() {
load(DEFAULT_POSITION);
27
}
function load(fen) {
var tokens = fen.split(/\s+/);
var position = tokens[0];
var square = 0;
if (!validate_fen(fen).valid) {
return false;
}
clear();
turn = tokens[1];
28
}
update_setup(generate_fen());
return true;
}
29
if (isNaN(tokens[5]) || (parseInt(tokens[5], 10) <= 0)) {
return {valid: false, error_number: 2, error: errors[2]};
}
30
return {valid: false, error_number: 8, error: errors[8]};
}
sum_fields += parseInt(rows[i][k], 10);
previous_was_number = true;
} else {
if (!/^[prnbqkPRNBQK]$/.test(rows[i][k])) {
return {valid: false, error_number: 9, error: errors[9]};
}
sum_fields += 1;
previous_was_number = false;
}
}
if (sum_fields !== 8) {
return {valid: false, error_number: 10, error: errors[10]};
}
}
/* everything's okay! */
return {valid: true, error_number: 0, error: errors[0]};
}
function generate_fen() {
var empty = 0;
var fen = '';
31
fen += (color === WHITE) ?
piece.toUpperCase() : piece.toLowerCase();
}
if (i !== SQUARES.h1) {
fen += '/';
}
empty = 0;
i += 8;
}
}
function set_header(args) {
for (var i = 0; i < args.length; i += 2) {
if (typeof args[i] === 'string' &&
typeof args[i + 1] === 'string') {
header[args[i]] = args[i + 1];
}
}
return header;
32
}
/* called when the initial board setup is changed with put() or remove().
* modifies the SetUp and FEN properties of the header object. if the
FEN is
* equal to the default position, the SetUp and FEN are deleted
* the setup is only updated if history.length is zero, ie moves haven't
been
* made.
*/
function update_setup(fen) {
if (history.length > 0) return;
function get(square) {
var piece = board[SQUARES[square]];
return (piece) ? {type: piece.type, color: piece.color} : null;
}
33
return false;
}
var sq = SQUARES[square];
update_setup(generate_fen());
return true;
}
function remove(square) {
var piece = get(square);
board[SQUARES[square]] = null;
if (piece && piece.type === KING) {
kings[piece.color] = EMPTY;
}
update_setup(generate_fen());
return piece;
}
34
};
if (promotion) {
move.flags |= BITS.PROMOTION;
move.promotion = promotion;
}
if (board[to]) {
move.captured = board[to].type;
} else if (flags & BITS.EP_CAPTURE) {
move.captured = PAWN;
}
return move;
}
function generate_moves(options) {
function add_move(board, moves, from, to, flags) {
/* if pawn promotion */
if (board[from].type === PAWN &&
(rank(to) === RANK_8 || rank(to) === RANK_1)) {
var pieces = [QUEEN, ROOK, BISHOP, KNIGHT];
for (var i = 0, len = pieces.length; i < len; i++) {
moves.push(build_move(board, from, to, flags, pieces[i]));
}
} else {
moves.push(build_move(board, from, to, flags));
}
}
35
options.legal : true;
/* double square */
var square = i + PAWN_OFFSETS[us][1];
if (second_rank[us] === rank(i) && board[square] == null) {
add_move(board, moves, i, square, BITS.BIG_PAWN);
}
}
/* pawn captures */
for (j = 2; j < 4; j++) {
var square = i + PAWN_OFFSETS[us][j];
if (square & 0x88) continue;
36
if (board[square] != null &&
board[square].color === them) {
add_move(board, moves, i, square, BITS.CAPTURE);
} else if (square === ep_square) {
add_move(board, moves, i, ep_square,
BITS.EP_CAPTURE);
}
}
} else {
for (var j = 0, len = PIECE_OFFSETS[piece.type].length; j < len;
j++) {
var offset = PIECE_OFFSETS[piece.type][j];
var square = i;
while (true) {
square += offset;
if (square & 0x88) break;
if (board[square] == null) {
add_move(board, moves, i, square, BITS.NORMAL);
} else {
if (board[square].color === us) break;
add_move(board, moves, i, square, BITS.CAPTURE);
break;
}
37
var castling_from = kings[us];
var castling_to = castling_from + 2;
/* queen-side castling */
if (castling[us] & BITS.QSIDE_CASTLE) {
var castling_from = kings[us];
var castling_to = castling_from - 2;
/* return all pseudo-legal moves (this includes moves that allow the
king
* to be captured)
*/
if (!legal) {
return moves;
}
38
for (var i = 0, len = moves.length; i < len; i++) {
make_move(moves[i]);
if (!king_attacked(us)) {
legal_moves.push(moves[i]);
}
undo_move();
}
return legal_moves;
}
39
output += algebraic(move.from)[0];
}
output += 'x';
}
output += algebraic(move.to);
make_move(move);
if (in_check()) {
if (in_checkmate()) {
output += '#';
} else {
output += '+';
}
}
undo_move();
return output;
}
40
var index = difference + 119;
return false;
}
function king_attacked(color) {
return attacked(swap_color(color), kings[color]);
}
function in_check() {
return king_attacked(turn);
}
function in_checkmate() {
41
return in_check() && generate_moves().length === 0;
}
function in_stalemate() {
return !in_check() && generate_moves().length === 0;
}
function insufficient_material() {
var pieces = {};
var bishops = [];
var num_pieces = 0;
var sq_color = 0;
/* k vs. k */
if (num_pieces === 2) { return true; }
/* kb vs. kb where any number of bishops are all on the same color */
else if (num_pieces === pieces[BISHOP] + 2) {
var sum = 0;
var len = bishops.length;
for (var i = 0; i < len; i++) {
42
sum += bishops[i];
}
if (sum === 0 || sum === len) { return true; }
}
return false;
}
function in_threefold_repetition() {
/* TODO: while this function is fine for casual use, a better
* implementation would use a Zobrist key (instead of FEN). the
* Zobrist key would be maintained in the make_move/undo_move
functions,
* avoiding the costly that we do below.
*/
var moves = [];
var positions = {};
var repetition = false;
while (true) {
var move = undo_move();
if (!move) break;
moves.push(move);
}
while (true) {
/* remove the last two fields in the FEN string, they're not needed
* when checking for draw by rep */
var fen = generate_fen().split(' ').slice(0,4).join(' ');
if (!moves.length) {
break;
}
make_move(moves.pop());
43
}
return repetition;
}
function push(move) {
history.push({
move: move,
kings: {b: kings.b, w: kings.w},
turn: turn,
castling: {b: castling.b, w: castling.w},
ep_square: ep_square,
half_moves: half_moves,
move_number: move_number
});
}
function make_move(move) {
var us = turn;
var them = swap_color(us);
push(move);
board[move.to] = board[move.from];
board[move.from] = null;
44
if (board[move.to].type === KING) {
kings[board[move.to].color] = move.to;
45
}
function undo_move() {
var old = history.pop();
if (old == null) { return null; }
46
var us = turn;
var them = swap_color(turn);
board[move.from] = board[move.to];
board[move.from].type = move.piece; // to undo any promotions
board[move.to] = null;
board[castling_to] = board[castling_from];
board[castling_from] = null;
}
return move;
}
47
var moves = generate_moves({legal: !sloppy});
var ambiguities = 0;
var same_rank = 0;
var same_file = 0;
/* if a move of the same piece type ends on the same to square, we'll
* need to add a disambiguator to the algebraic notation
*/
if (piece === ambig_piece && from !== ambig_from && to ===
ambig_to) {
ambiguities++;
if (ambiguities > 0) {
/* if there exists a similar moving piece on the same rank and file as
* the move in question, use the square as the disambiguator
*/
if (same_rank > 0 && same_file > 0) {
return algebraic(from);
}
48
/* if the moving piece rests on the same file, use the rank symbol as
the
* disambiguator
*/
else if (same_file > 0) {
return algebraic(from).charAt(1);
}
/* else use the file symbol */
else {
return algebraic(from).charAt(0);
}
}
return '';
}
function ascii() {
var s = ' +------------------------+\n';
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
/* display the rank */
if (file(i) === 0) {
s += ' ' + '87654321'[rank(i)] + ' |';
}
/* empty piece */
if (board[i] == null) {
s += ' . ';
} else {
var piece = board[i].type;
var color = board[i].color;
var symbol = (color === WHITE) ?
piece.toUpperCase() : piece.toLowerCase();
s += ' ' + symbol + ' ';
}
49
s += ' +------------------------+\n';
s += ' a b c d e f g h\n';
return s;
}
// if we're using the sloppy parser run a regex to grab piece, to, and
from
// this should parse invalid SAN like: Pe2-e4, Rc1c4, Qf3xf7
if (sloppy) {
var matches = clean_move.match(/([pnbrqkPNBRQK])?([a-h][1-
8])x?-?([a-h][1-8])([qrbnQRBN])?/);
if (matches) {
var piece = matches[1];
var from = matches[2];
var to = matches[3];
var promotion = matches[4];
}
}
50
(!promotion || promotion.toLowerCase() ==
moves[i].promotion)) {
return moves[i];
}
}
}
return null;
}
/****************************************************************
*************
* UTILITY FUNCTIONS
****************************************************************
************/
function rank(i) {
return i >> 4;
}
function file(i) {
return i & 15;
}
function algebraic(i){
var f = file(i), r = rank(i);
return 'abcdefgh'.substring(f,f+1) + '87654321'.substring(r,r+1);
}
function swap_color(c) {
return c === WHITE ? BLACK : WHITE;
}
function is_digit(c) {
return '0123456789'.indexOf(c) !== -1;
}
51
function make_pretty(ugly_move) {
var move = clone(ugly_move);
move.san = move_to_san(move, false);
move.to = algebraic(move.to);
move.from = algebraic(move.from);
return move;
}
function clone(obj) {
var dupe = (obj instanceof Array) ? [] : {};
return dupe;
}
function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}
/****************************************************************
*************
* DEBUGGING UTILITIES
52
****************************************************************
************/
function perft(depth) {
var moves = generate_moves({legal: false});
var nodes = 0;
var color = turn;
return nodes;
}
return {
/****************************************************************
***********
* PUBLIC CONSTANTS (is there a better way to do this?)
****************************************************************
**********/
WHITE: WHITE,
BLACK: BLACK,
PAWN: PAWN,
KNIGHT: KNIGHT,
BISHOP: BISHOP,
ROOK: ROOK,
QUEEN: QUEEN,
KING: KING,
53
SQUARES: (function() {
/* from the ECMA-262 spec (section 12.6.4):
* "The mechanics of enumerating the properties ... is
* implementation dependent"
* so: for (var sq in SQUARES) { keys.push(sq); } might not be
* ordered correctly
*/
var keys = [];
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
if (i & 0x88) { i += 7; continue; }
keys.push(algebraic(i));
}
return keys;
})(),
FLAGS: FLAGS,
/****************************************************************
***********
* PUBLIC API
****************************************************************
**********/
load: function(fen) {
return load(fen);
},
reset: function() {
return reset();
},
moves: function(options) {
/* The internal representation of a chess move is in 0x88 format,
and
* not meant to be human-readable. The code below converts the
0x88
* square coordinates to algebraic coordinates. It also prunes an
* unnecessary move keys resulting from a verbose call.
*/
54
var ugly_moves = generate_moves(options);
var moves = [];
/* does the user want a full move object (most likely not), or just
* SAN
*/
if (typeof options !== 'undefined' && 'verbose' in options &&
options.verbose) {
moves.push(make_pretty(ugly_moves[i]));
} else {
moves.push(move_to_san(ugly_moves[i], false));
}
}
return moves;
},
ugly_moves: function(options) {
var ugly_moves = generate_moves(options);
return ugly_moves;
},
in_check: function() {
return in_check();
},
in_checkmate: function() {
return in_checkmate();
},
in_stalemate: function() {
return in_stalemate();
},
in_draw: function() {
return half_moves >= 100 ||
in_stalemate() ||
insufficient_material() ||
55
in_threefold_repetition();
},
insufficient_material: function() {
return insufficient_material();
},
in_threefold_repetition: function() {
return in_threefold_repetition();
},
game_over: function() {
return half_moves >= 100 ||
in_checkmate() ||
in_stalemate() ||
insufficient_material() ||
in_threefold_repetition();
},
validate_fen: function(fen) {
return validate_fen(fen);
},
fen: function() {
return generate_fen();
},
board: function() {
var output = [],
row = [];
56
i += 8;
}
}
return output;
},
pgn: function(options) {
/* using the specification from http://www.chessclub.com/help/PGN-
spec
* example for html usage: .pgn({ max_width: 72, newline_char:
"<br />" })
*/
var newline = (typeof options === 'object' &&
typeof options.newline_char === 'string') ?
options.newline_char : '\n';
var max_width = (typeof options === 'object' &&
typeof options.max_width === 'number') ?
options.max_width : 0;
var result = [];
var header_exists = false;
57
var moves = [];
var move_string = '';
/* if the position started with black to move, start PGN with 1. ...
*/
if (!history.length && move.color === 'b') {
move_string = move_number + '. ...';
} else if (move.color === 'w') {
/* store the previous generated move_string if we have one */
if (move_string.length) {
moves.push(move_string);
}
move_string = move_number + '.';
}
/* is there a result? */
if (typeof header.Result !== 'undefined') {
moves.push(header.Result);
}
58
}
result.push(newline);
current_width = 0;
} else if (i !== 0) {
result.push(' ');
current_width++;
}
result.push(moves[i]);
current_width += moves[i].length;
}
return result.join('');
},
function mask(str) {
return str.replace(/\\/g, '\\');
}
function has_keys(object) {
for (var key in object) {
59
return true;
}
return false;
}
return header_obj;
}
60
reset();
/* delete comments */
ms = ms.replace(/(\{[^}]+\})+?/g, '');
61
/* delete empty entries */
moves = moves.join(',').replace(/,,+/g, ',').split(',');
var move = '';
header: function() {
return set_header(arguments);
62
},
ascii: function() {
return ascii();
},
turn: function() {
return turn;
},
// allow the user to specify the sloppy move parser to work around
over
// disambiguation bugs in Fritz and Chessbase
var sloppy = (typeof options !== 'undefined' && 'sloppy' in options)
?
options.sloppy : false;
63
move.to === algebraic(moves[i].to) &&
(!('promotion' in moves[i]) ||
move.promotion === moves[i].promotion)) {
move_obj = moves[i];
break;
}
}
}
make_move(move_obj);
return pretty_move;
},
return pretty_move;
},
undo: function() {
var move = undo_move();
return (move) ? make_pretty(move) : null;
},
clear: function() {
return clear();
},
64
put: function(piece, square) {
return put(piece, square);
},
get: function(square) {
return get(square);
},
remove: function(square) {
return remove(square);
},
perft: function(depth) {
return perft(depth);
},
square_color: function(square) {
if (square in SQUARES) {
var sq_0x88 = SQUARES[square];
return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'light' :
'dark';
}
return null;
},
history: function(options) {
var reversed_history = [];
var move_history = [];
var verbose = (typeof options !== 'undefined' && 'verbose' in
options &&
options.verbose);
65
if (verbose) {
move_history.push(make_pretty(move));
} else {
move_history.push(move_to_san(move));
}
make_move(move);
}
return move_history;
}
};
};
File: Chessboard.js
/*!
* chessboard.js v0.3.0
*
* Copyright 2013 Chris Oakman
* Released under the MIT license
* http://chessboardjs.com/license
*
* Date: 10 Aug 2013
*/
//------------------------------------------------------------------------------
// Chess Util Functions
//------------------------------------------------------------------------------
var COLUMNS = 'abcdefgh'.split('');
function validMove(move) {
66
// move should be a string
if (typeof move !== 'string') return false;
function validSquare(square) {
if (typeof square !== 'string') return false;
return (square.search(/^[a-h][1-8]$/) !== -1);
}
function validPieceCode(code) {
if (typeof code !== 'string') return false;
return (code.search(/^[bw][KQRNBP]$/) !== -1);
}
// TODO: this whole function could probably be replaced with a single regex
function validFen(fen) {
if (typeof fen !== 'string') return false;
// cut off any move, castling, etc info from the end
// we're only interested in position information
fen = fen.replace(/ .+$/, '');
return true;
}
function validPositionObject(pos) {
if (typeof pos !== 'object') return false;
67
for (var i in pos) {
if (pos.hasOwnProperty(i) !== true) continue;
return true;
}
// white piece
return 'w' + piece.toUpperCase();
}
// white piece
if (tmp[0] === 'w') {
return tmp[1].toUpperCase();
}
// black piece
return tmp[1].toLowerCase();
}
// cut off any move, castling, etc info from the end
// we're only interested in position information
fen = fen.replace(/ .+$/, '');
68
var position = {};
var currentRow = 8;
for (var i = 0; i < 8; i++) {
var row = rows[i].split('');
var colIndex = 0;
currentRow--;
}
return position;
}
var currentRow = 8;
for (var i = 0; i < 8; i++) {
for (var j = 0; j < 8; j++) {
var square = COLUMNS[j] + currentRow;
// piece exists
if (obj.hasOwnProperty(square) === true) {
fen += pieceCodeToFen(obj[square]);
}
69
// empty space
else {
fen += '1';
}
}
if (i !== 7) {
fen += '/';
}
currentRow--;
}
return fen;
}
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
// use unique class names to prevent clashing with anything else on the page
// and simplify selectors
var CSS = {
alpha: 'alpha-d2270',
black: 'black-3c85d',
board: 'board-b72b1',
chessboard: 'chessboard-63f37',
clearfix: 'clearfix-7da63',
70
highlight1: 'highlight1-32417',
highlight2: 'highlight2-9c5d2',
notation: 'notation-322f9',
numeric: 'numeric-fc462',
piece: 'piece-417db',
row: 'row-5277c',
sparePieces: 'spare-pieces-7492f',
sparePiecesBottom: 'spare-pieces-bottom-ae20f',
sparePiecesTop: 'spare-pieces-top-4028b',
square: 'square-55d63',
white: 'white-1e1d7'
};
//------------------------------------------------------------------------------
// Module Scope Variables
//------------------------------------------------------------------------------
// DOM elements
var containerEl,
boardEl,
draggedPieceEl,
sparePiecesTopEl,
sparePiecesBottomEl;
//------------------------------------------------------------------------------
// Stateful
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// JS Util Functions
//------------------------------------------------------------------------------
71
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
function createId() {
return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function(c) {
var r = Math.random() * 16 | 0;
return r.toString(16);
});
}
function deepCopy(thing) {
return JSON.parse(JSON.stringify(thing));
}
function parseSemVer(version) {
var tmp = version.split('.');
return {
major: parseInt(tmp[0], 10),
minor: parseInt(tmp[1], 10),
patch: parseInt(tmp[2], 10)
};
}
//------------------------------------------------------------------------------
// Validation / Errors
//------------------------------------------------------------------------------
72
// print to console
if (cfg.showErrors === 'console' &&
typeof console === 'object' &&
typeof console.log === 'function') {
console.log(errorText);
if (arguments.length >= 2) {
console.log(obj);
}
return;
}
// alert errors
if (cfg.showErrors === 'alert') {
if (obj) {
errorText += '\n\n' + JSON.stringify(obj);
}
window.alert(errorText);
return;
}
// custom function
if (typeof cfg.showErrors === 'function') {
cfg.showErrors(code, msg, obj);
}
}
// check dependencies
function checkDeps() {
// if containerId is a string, it must be the ID of a DOM node
if (typeof containerElOrId === 'string') {
// cannot be empty
if (containerElOrId === '') {
window.alert('ChessBoard Error 1001: ' +
'The first argument to ChessBoard() cannot be an empty string.' +
'\n\nExiting...');
return false;
}
73
// set the containerEl
containerEl = $(el);
}
if (containerEl.length !== 1) {
window.alert('ChessBoard Error 1003: The first argument to ' +
'ChessBoard() must be an ID or a single DOM node.' +
'\n\nExiting...');
return false;
}
}
return true;
}
function validAnimationSpeed(speed) {
if (speed === 'fast' || speed === 'slow') {
return true;
}
74
}
75
typeof cfg.pieceTheme !== 'function')) {
cfg.pieceTheme = 'img/chesspieces/wikipedia/{piece}.png';
}
// animation speeds
if (cfg.hasOwnProperty('appearSpeed') !== true ||
validAnimationSpeed(cfg.appearSpeed) !== true) {
cfg.appearSpeed = 200;
}
if (cfg.hasOwnProperty('moveSpeed') !== true ||
validAnimationSpeed(cfg.moveSpeed) !== true) {
cfg.moveSpeed = 200;
}
if (cfg.hasOwnProperty('snapbackSpeed') !== true ||
validAnimationSpeed(cfg.snapbackSpeed) !== true) {
cfg.snapbackSpeed = 50;
}
if (cfg.hasOwnProperty('snapSpeed') !== true ||
validAnimationSpeed(cfg.snapSpeed) !== true) {
cfg.snapSpeed = 25;
}
if (cfg.hasOwnProperty('trashSpeed') !== true ||
validAnimationSpeed(cfg.trashSpeed) !== true) {
cfg.trashSpeed = 100;
}
else {
error(7263, 'Invalid value passed to config.position.', cfg.position);
}
}
return true;
}
76
//------------------------------------------------------------------------------
// DOM Misc
//------------------------------------------------------------------------------
// spare pieces
var pieces = 'KQRBNP'.split('');
for (var i = 0; i < pieces.length; i++) {
var whitePiece = 'w' + pieces[i];
var blackPiece = 'b' + pieces[i];
SPARE_PIECE_ELS_IDS[whitePiece] = whitePiece + '-' + createId();
SPARE_PIECE_ELS_IDS[blackPiece] = blackPiece + '-' + createId();
}
}
77
//------------------------------------------------------------------------------
// Markup Building
//------------------------------------------------------------------------------
function buildBoardContainer() {
var html = '<div class="' + CSS.chessboard + '">';
html += '</div>';
return html;
}
/*
var buildSquare = function(color, size, id) {
var html = '<div class="' + CSS.square + ' ' + CSS[color] + '" ' +
'style="width: ' + size + 'px; height: ' + size + 'px" ' +
'id="' + id + '">';
html += '</div>';
return html;
};
*/
function buildBoard(orientation) {
if (orientation !== 'black') {
orientation = 'white';
}
78
// algebraic notation / orientation
var alpha = deepCopy(COLUMNS);
var row = 8;
if (orientation === 'black') {
alpha.reverse();
row = 1;
}
// numeric notation
if (j === 0) {
html += '<div class="' + CSS.notation + ' ' + CSS.numeric + '">' +
row + '</div>';
}
}
79
else {
row++;
}
}
return html;
}
function buildPieceImgSrc(piece) {
if (typeof cfg.pieceTheme === 'function') {
return cfg.pieceTheme(piece);
}
return html;
}
function buildSparePieces(color) {
var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP'];
if (color === 'black') {
pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP'];
}
80
for (var i = 0; i < pieces.length; i++) {
html += buildPiece(pieces[i], false, SPARE_PIECE_ELS_IDS[pieces[i]]);
}
return html;
}
//------------------------------------------------------------------------------
// Animations
//------------------------------------------------------------------------------
// on complete
var complete = function() {
// add the "real" piece to the destination square
destSquareEl.append(buildPiece(piece));
81
// animate the piece to the destination square
var opts = {
duration: cfg.moveSpeed,
complete: complete
};
animatedPieceEl.animate(destSquarePosition, opts);
}
// on complete
var complete = function() {
// add the "real" piece to the destination square
destSquareEl.find('.' + CSS.piece).remove();
destSquareEl.append(buildPiece(piece));
82
// execute an array of animations
function doAnimations(a, oldPos, newPos) {
ANIMATION_HAPPENING = true;
var numFinished = 0;
function onFinish() {
numFinished++;
drawPositionInstant();
ANIMATION_HAPPENING = false;
// move a piece
if (a[i].type === 'move') {
animateSquareToSquare(a[i].source, a[i].destination, a[i].piece,
onFinish);
}
}
83
}
s2 = s2.split('');
var s2x = COLUMNS.indexOf(s2[0]) + 1;
var s2y = parseInt(s2[1], 10);
squares.push({
square: s,
distance: squareDistance(square, s)
});
}
}
// sort by distance
squares.sort(function(a, b) {
return a.distance - b.distance;
});
84
}
return squares2;
}
return false;
}
85
var closestPiece = findClosestPiece(pos1, pos2[i], i);
if (closestPiece !== false) {
animations.push({
type: 'move',
source: closestPiece,
destination: i,
piece: pos2[i]
});
delete pos1[closestPiece];
delete pos2[i];
squaresMovedTo[i] = true;
}
}
animations.push({
type: 'add',
square: i,
piece: pos2[i]
})
delete pos2[i];
}
animations.push({
type: 'clear',
square: i,
piece: pos1[i]
});
delete pos1[i];
}
return animations;
}
86
//------------------------------------------------------------------------------
// Control Flow
//------------------------------------------------------------------------------
function drawPositionInstant() {
// clear the board
boardEl.find('.' + CSS.piece).remove();
$('#' + SQUARE_ELS_IDS[i]).append(buildPiece(CURRENT_POSITION[i]));
}
}
function drawBoard() {
boardEl.html(buildBoard(CURRENT_ORIENTATION));
drawPositionInstant();
// skip the move if the position doesn't have a piece on the source square
if (position.hasOwnProperty(i) !== true) continue;
87
}
return position;
}
function setCurrentPosition(position) {
var oldPos = deepCopy(CURRENT_POSITION);
var newPos = deepCopy(position);
var oldFen = objToFen(oldPos);
var newFen = objToFen(newPos);
// update state
CURRENT_POSITION = position;
}
function isXYOnSquare(x, y) {
for (var i in SQUARE_ELS_OFFSETS) {
if (SQUARE_ELS_OFFSETS.hasOwnProperty(i) !== true) continue;
var s = SQUARE_ELS_OFFSETS[i];
if (x >= s.left && x < s.left + SQUARE_SIZE &&
y >= s.top && y < s.top + SQUARE_SIZE) {
return i;
}
}
return 'offboard';
}
88
}
function removeSquareHighlights() {
boardEl.find('.' + CSS.square)
.removeClass(CSS.highlight1 + ' ' + CSS.highlight2);
}
function snapbackDraggedPiece() {
// there is no "snapback" for spare pieces
if (DRAGGED_PIECE_SOURCE === 'spare') {
trashDraggedPiece();
return;
}
removeSquareHighlights();
// animation complete
function complete() {
drawPositionInstant();
draggedPieceEl.css('display', 'none');
// set state
DRAGGING_A_PIECE = false;
}
function trashDraggedPiece() {
removeSquareHighlights();
89
// remove the source piece
var newPosition = deepCopy(CURRENT_POSITION);
delete newPosition[DRAGGED_PIECE_SOURCE];
setCurrentPosition(newPosition);
// set state
DRAGGING_A_PIECE = false;
}
function dropDraggedPieceOnSquare(square) {
removeSquareHighlights();
// update position
var newPosition = deepCopy(CURRENT_POSITION);
delete newPosition[DRAGGED_PIECE_SOURCE];
newPosition[square] = DRAGGED_PIECE;
setCurrentPosition(newPosition);
// animation complete
var complete = function() {
drawPositionInstant();
draggedPieceEl.css('display', 'none');
// set state
90
DRAGGING_A_PIECE = false;
}
// set state
DRAGGING_A_PIECE = true;
DRAGGED_PIECE = piece;
DRAGGED_PIECE_SOURCE = source;
function updateDraggedPiece(x, y) {
// put the dragged piece over the mouse cursor
draggedPieceEl.css({
91
left: x - (SQUARE_SIZE / 2),
top: y - (SQUARE_SIZE / 2)
});
// get location
var location = isXYOnSquare(x, y);
// run onDragMove
if (typeof cfg.onDragMove === 'function') {
cfg.onDragMove(location, DRAGGED_PIECE_LOCATION,
DRAGGED_PIECE_SOURCE, DRAGGED_PIECE,
deepCopy(CURRENT_POSITION), CURRENT_ORIENTATION);
}
// update state
DRAGGED_PIECE_LOCATION = location;
}
function stopDraggedPiece(location) {
// determine what the action should be
var action = 'drop';
if (location === 'offboard' && cfg.dropOffBoard === 'snapback') {
action = 'snapback';
}
if (location === 'offboard' && cfg.dropOffBoard === 'trash') {
action = 'trash';
}
// run their onDrop function, which can potentially change the drop action
if (cfg.hasOwnProperty('onDrop') === true &&
typeof cfg.onDrop === 'function') {
var newPosition = deepCopy(CURRENT_POSITION);
92
// source piece is a spare piece and position is off the board
//if (DRAGGED_PIECE_SOURCE === 'spare' && location === 'offboard') {...}
// position has not changed; do nothing
// source piece was on the board and position is off the board
if (validSquare(DRAGGED_PIECE_SOURCE) === true && location === 'offboard') {
// remove the piece from the board
delete newPosition[DRAGGED_PIECE_SOURCE];
}
// do it!
if (action === 'snapback') {
snapbackDraggedPiece();
}
else if (action === 'trash') {
trashDraggedPiece();
}
else if (action === 'drop') {
dropDraggedPieceOnSquare(location);
}
}
//------------------------------------------------------------------------------
// Public Methods
93
//------------------------------------------------------------------------------
/*
// get or set config properties
// TODO: write this, GitHub Issue #1
widget.config = function(arg1, arg2) {
// get the current config
if (arguments.length === 0) {
return deepCopy(cfg);
}
};
*/
// flip orientation
widget.flip = function() {
widget.orientation('flip');
};
/*
// TODO: write this, GitHub Issue #5
widget.highlight = function() {
};
*/
// move pieces
widget.move = function() {
94
// no need to throw an error here; just do nothing
if (arguments.length === 0) return;
widget.orientation = function(arg) {
// no arguments, return the current orientation
if (arguments.length === 0) {
return CURRENT_ORIENTATION;
}
95
// flip orientation
if (arg === 'flip') {
CURRENT_ORIENTATION = (CURRENT_ORIENTATION === 'white') ? 'black' : 'white';
drawBoard();
return;
}
// start position
if (typeof position === 'string' && position.toLowerCase() === 'start') {
position = deepCopy(START_POSITION);
}
96
// set the new position
setCurrentPosition(position);
}
// instant update
else {
setCurrentPosition(position);
drawPositionInstant();
}
};
widget.resize = function() {
// calulate the new square size
SQUARE_SIZE = calculateSquareSize();
// spare pieces
if (cfg.sparePieces === true) {
containerEl.find('.' + CSS.sparePieces)
.css('paddingLeft', (SQUARE_SIZE + BOARD_BORDER_SIZE) + 'px');
}
//------------------------------------------------------------------------------
// Browser Events
//------------------------------------------------------------------------------
function isTouchDevice() {
return ('ontouchstart' in document.documentElement);
}
// reference: http://www.quirksmode.org/js/detect.html
97
function isMSIE() {
return (navigator && navigator.userAgent &&
navigator.userAgent.search(/MSIE/) !== -1);
}
function stopDefault(e) {
e.preventDefault();
}
function mousedownSquare(e) {
// do nothing if we're not draggable
if (cfg.draggable !== true) return;
function touchstartSquare(e) {
// do nothing if we're not draggable
if (cfg.draggable !== true) return;
e = e.originalEvent;
beginDraggingPiece(square, CURRENT_POSITION[square],
e.changedTouches[0].pageX, e.changedTouches[0].pageY);
}
function mousedownSparePiece(e) {
// do nothing if sparePieces is not enabled
if (cfg.sparePieces !== true) return;
98
beginDraggingPiece('spare', piece, e.pageX, e.pageY);
}
function touchstartSparePiece(e) {
// do nothing if sparePieces is not enabled
if (cfg.sparePieces !== true) return;
e = e.originalEvent;
beginDraggingPiece('spare', piece,
e.changedTouches[0].pageX, e.changedTouches[0].pageY);
}
function mousemoveWindow(e) {
// do nothing if we are not dragging a piece
if (DRAGGING_A_PIECE !== true) return;
updateDraggedPiece(e.pageX, e.pageY);
}
function touchmoveWindow(e) {
// do nothing if we are not dragging a piece
if (DRAGGING_A_PIECE !== true) return;
updateDraggedPiece(e.originalEvent.changedTouches[0].pageX,
e.originalEvent.changedTouches[0].pageY);
}
function mouseupWindow(e) {
// do nothing if we are not dragging a piece
if (DRAGGING_A_PIECE !== true) return;
stopDraggedPiece(location);
}
function touchendWindow(e) {
// do nothing if we are not dragging a piece
if (DRAGGING_A_PIECE !== true) return;
99
var location = isXYOnSquare(e.originalEvent.changedTouches[0].pageX,
e.originalEvent.changedTouches[0].pageY);
stopDraggedPiece(location);
}
function mouseenterSquare(e) {
// do not fire this event if we are dragging a piece
// NOTE: this should never happen, but it's a safeguard
if (DRAGGING_A_PIECE !== false) return;
function mouseleaveSquare(e) {
// do not fire this event if we are dragging a piece
// NOTE: this should never happen, but it's a safeguard
if (DRAGGING_A_PIECE !== false) return;
100
if (CURRENT_POSITION.hasOwnProperty(square) === true) {
piece = CURRENT_POSITION[square];
}
//------------------------------------------------------------------------------
// Initialization
//------------------------------------------------------------------------------
function addEvents() {
// prevent browser "image drag"
$('body').on('mousedown mousemove', '.' + CSS.piece, stopDefault);
// IE doesn't like the events on the window object, but other browsers
// perform better that way
if (isMSIE() === true) {
// IE-specific prevent browser "image drag"
document.ondragstart = function() { return false; };
$('body').on('mousemove', mousemoveWindow);
$('body').on('mouseup', mouseupWindow);
}
else {
$(window).on('mousemove', mousemoveWindow);
$(window).on('mouseup', mouseupWindow);
}
101
}
}
function initDom() {
// build board and save it in memory
containerEl.html(buildBoardContainer());
boardEl = containerEl.find('.' + CSS.board);
function init() {
if (checkDeps() !== true ||
expandConfig() !== true) return;
initDom();
addEvents();
}
// go time
init();
}; // end window.ChessBoard
102
})(); // end anonymous wrapper
7. Testing
Testing software is a critical element of software quality assurance and represents the ultimate
review of specification, design and code generation.
Testing Objectives
2. A good test case is one that has a high probability of finding an undiscovered error.
In black box testing, the system is tested as whole without considering the internal
process and their inputs and outputs. Black box tests are used to demonstrate that software
functions are operational, that input is properly accepted and output is correctly produced, and
that the integrity of external information is maintained.
White box testing is a technique that is done to verify the internal process, their inputs
and outputs. Hence the test cases are derived using the control structure of procedural design.
The system test reducing the test case design. Each function is executed separately and results
are found to be as expected lines. The fields which are compulsory are validated. It is found that
all the functions are working satisfactorily according to the specifications.
103
1 Opening The Chess Chess Success
index file Board Board is
should be generated
generated
3 Check if AI AI Success
the moves generated generated
AI is doing outputs outputs
is valid should worked
work properly
properly
Output Screenshots:
104
105
106
107
108
9. Conclusion
This project was completed in fulfillment of the course requirement of ADA, Algorithm
Design and Analysis. It provided a very good opportunity to obtain both the theoretical
knowledge and practical experience. It helped us to get more familiar with the
development flow of gaming systems. The result of our project meet both the
requirement of the course and our expectation. A competitive game between human user
and the machine was realized. This project began as fun experiment to see if we
could develop a chess program that would make legal, albeit uninformed
moves. It needs to have the functionality to best, or at least put up a strong fight
against us in a game of chess.
In addition the development of the core program, we experimented with some
simple evaluation functions and the initial results seemed to confirm what had
been covered in the introductory Minimax Algorithm while considerations like
safety and mobility can affect machine choices, material value and search depth
tend to hold the most weight in such decisions. Perhaps the largest future
extension we would propose is the addition of opening and end game databases
in order to make it perform more competitively in these areas. It features a user-
friendly interface.
This project provides, to the best of our knowledge, the first methodology of automatic
learning the parameters of the evaluation function.
10. References
https://inst.eecs.berkeley.edu/~cs162/sp07/Nachos/chess.shtml
https://www.cmpe.boun.edu.tr/~gungort/undergraduateprojects/Developing%20an%20Adaptive
%20Chess%20Program.pdf
https://www.scribd.com/document/168302488/Chess-game-implementation-in-Java
109
http://www2.eng.cam.ac.uk/~tpl/chess/David_Hammond/Dissertation.html
https://en.wikipedia.org/wiki/Backtracking
https://en.wikipedia.org/wiki/minimax
https://en.wikipedia.org/wiki/HTML
110