Table of Contents
As a programmer, I always thought that developing a chess game would be a big milestone and I would be so proud of it.
And here I am, presenting you with a chess game. And yes, I'm a little proud of it. But wait. Have I really developed a chess
game?
First of all, creating a chess game means nothing less than developing the chess engine,
which is the smarty-pants piece of software sitting behind the board, trying to come up with the best moves and pretending to be a human. I have
always been a poor chess player, and my first thought was that the best thing I could do to develop a chess engine would be porting from some
existing and working C chess engine. The target language would be one of the few which I feel comfortable with: C# or JavaScript.
Much to my surprise, recently I discovered that a brilliant Mexican guy named Óscar Toledo Gutiérrez had already developed
a JavaScript chess engine, after having developed a C and Java versions of it. Not only that: Toledo managed to squeeze his
C chess engine, named Nano Chess, in only 1257 characters, becoming the
world's smallest C chess engine. Toledo perfected it further and his Pico Chess C code amounts to less than 1Kb. He is
five times winner of the IOCCC (International Obfuscated C Code Contest). After some thought, I decided not to write the chess engine by myself.
Chesslings is a chess game based on Toledo's Tiny Chess. That is, literally
based on it. Tiny Chess actually runs silently behind the scenes, providing all the game logic we need. What Chesslings
really does is provide a different look and feel by isometric projection
and animations for each move.
This article contains the code needed to run a website relying on no programming languages other than JavaScript, and consequently without references to assemblies or C# code,
so it doesn't need to be compiled. You can simply download the source code and open the HTML file in the browser of your preference.
For this project, we use Paper JS as the rendering framework. Paper.js is an open source vector graphics scripting framework that runs on top of the HTML5 Canvas.
This project will not use the most noticeable feature of Paper JS, i.e. the vector graphics, but takes advantage of the Paper JS's built-in render loop and ability to
render images on the html5 canvas
.
Throughout the code, you will notice many references to types defined by the Paper JS framework. Here goes a short explanation of how we use them in the project:
- paper.setup: Sets up an empty project for us. If a canvas is provided, it also creates a View for it, both linked to this scope.
The "project" in question is an instance of the
project
created internally by the Paper JS framework. It's enough to say that
it accepts the canvas residing on our page as a parameter, and manage all output to that canvas (like rendering images) and control user
interactions such as keyboard strokes.
- paper.Point: a two-dimensional object representing a point on the canvas. It is extensively employed in our project to hold and
calculate positions on the screen.
- paper.Rectangle:
- paper.view.center: the center of the
paper.view
object, which also means the point at the center of our canvas. - paper.Key.isDown: a boolean function that checks whether the keyboard Down key is pressed at that particular moment.
- paper.Raster: a raster is a specialized kind of
Item
object that represents a image in the Paper JS project. We use
it to create independent images for each of our chess pieces.
- paper.view.onFrame: Item level handler function to be called on each frame of an animation. This function gives us information
about how many times the event was fired, the total amount of time passed since the first frame event, and the time passed since the
last frame occurred. It is extensively used by our chess animations.
- paper.tool.onMouseUp: The function to be called when the mouse button is released over the item. The function receives a MouseEvent
object which contains information about the mouse event.
As I mentioned in the article introduction, Óscar Toledo ported his C code "Nano Chess" to JavaScript short after he had won the IOCCC
(International Obfuscated C Code Competition) prize, and this resulted in what he called Tiny Chess. As expected,
the code is heavily obfuscated, and surprisingly short for a chess engine. Here is the full JavaScript source of Tiny Chess:
var B,i,y,u,b,I=[],G=120,x=10,z=15,M=1e4,l=[5,3,4,6,2,4,3,5,1,1,1,1,1,1,1,1,9,9
,9,9,9,9,9,9,13,11,12,14,10,12,11,13,0,99,0,306,297,495,846,-1,0,1,2,2,1,0,-1,-
1,1,-10,10,-11,-9,9,11,10,20,-9,-11,-10,-20,-21,-19,-12,-8,8,12,19,21];function
X(w,c,h,e,S,s){var t,o,L,E,d,O=e,N=-M*M,K=78-h<<x,p,g,n,m,A,q,r,C,J,a=y?-x:x;
y^=8;G++;d=w||s&&s>=h&&X(0,0,0,21,0,0)>M;do{if(o=I[p=O]){q=o&z^y;if(q<7){A=q--&
2?8:4;C=o-9&z?[53,47,61,51,47,47][q]:57;do{r=I[p+=l[C]];if(!w|p==w){g=q|p+a-S?0
:S;if(!r&(!!q|A<3||!!g)||(r+1&z^y)>9&&q|A>2){if(m=!(r-2&7))return y^=8,I[G--]=
O,K;J=n=o&z;E=I[p-a]&z;t=q|E-7?n:(n+=2,6^y);while(n<=t){L=r?l[r&7|32]-h-q:0;if(
s)L+=(1-q?l[(p-p%x)/x+37]-l[(O-O%x)/x+37]+l[p%x+38]*(q?1:2)-l[O%x+38]+(o&16)/2:
!!m*9)+(!q?!(I[p-1]^n)+!(I[p+1]^n)+l[n&7|32]-99+!!g*99+(A<2):0)+!(E^y^9);if(s>h
||1<s&s==h&&L>z|d){I[p]=n,I[O]=m?(I[g]=I[m],I[m]=0):g?I[g]=0:0;L-=X(s>h|d?0:p,L
-N,h+1,I[G+1],J=q|A>1?0:p,s);if(!(h||s-1|B-O|i-n|p-b|L<-M))return W(),G--,u=J;
J=q-1|A<7||m||!s|d|r|o<z||X(0,0,0,21,0,0)>M;I[O]=o;I[p]=r;m?(I[m]=I[g],I[g]=0):
g?I[g]=9^y:0;}if(L>N||s>1&&L==N&&!h&&Math.random()<.5){I[G]=O;if(s>1){if(h&&c-L
<0)return y^=8,G--,L;if(!h)i=n,B=O,b=p;}N=L;}n+=J||(g=p,m=p<O?g-3:g+2,I[m]<z|I[
m+O-p]||I[p+=p-O])?1:0;}}}}while(!r&q>2||(p=O,q|A>2|o>z&!r&&++C*--A));}}}while(
++O>98?O=20:e-O);return y^=8,G--,N+M*M&&N>-K+1924|d?N:0;}B=i=y=u=0;while(B++<
120)I[B-1]=B%x?B/x%x<2|B%x<2?7:B/x&4?0:l[i++]|16:7;for(a=
"<table cellspacing=0 align=center>",i=18;i<100;a+=++i%10-9?
"<th width=40 height=40 onclick=Y("+i+") style='border:2px solid #aae' id=o"+i+
" bgcolor=#"+(i*.9&1?"9090d0>":"c0c0ff>"):(i++,"<tr>"));
a+="<th colspan=8><select id=t><option>Q<option>R<option>B";
document.write(a+"<option>N</select></table>");
function W(){B=b;for(p=21;p<99;++p)if(q=document.getElementById("o"+p)){q.
innerHTML="<img width=40 src="+(I[p]&z)+".gif>";q.
style.borderColor=p==B?"#ff0":"#aae";}}W();
function Y(s){i=(I[s]^y)&z;if(i>8){b=s;W();}else if(B&&i<9){b=s;i=I[B]&z;if((i&
7)==1&(b<29|b>90))i=14-document.getElementById("t").selectedIndex^y;X(0,0,0,21,
u,1);if(y)setTimeout("X(0,0,0,21,u,2/*ply*/),X(0,0,0,21,u,1)",250);}}
That´s it. That is all you need to run a full chess game in JavaScript. Now, where does our Chesslings game enter?
First, we create a dedicated div
for Tiny Chess, and give it an id equals to "toledoTable":
<div id="toledoTable" style="position: absolute; top: 0; display: none;"></<div>
Now we change Tiny Chess code a little, just enough to inject some notifications to our Chesslings game whenever
a new game movement occurs, and renaming some functions and variables for the sake of readability. This is needed because,
in fact, all game movements are actually made by the background running Tiny Chess code.
function renderChess() {
i = "<table>";
for (u = 18;
u < 98;
render()
)
B = selectedTileNum;
if (game) {
game.processMovement(I);
}
}
renderChess()
function render() {
$('#toledoTable').html(i += ++u % x - 9 ?
"<th width=30 height=30 onclick='clickTile(" + u + ")' style='font-size:25px'bgcolor=#" + (u - B ? u * .9 & 1 || 9 :
"d") + "0f0e0>&#" + (I[u] ? 9808 + l[67 + I[u]] : 160) + ";" : u++ && "<tr>");
}
function clickTile(u) {
game.onTileClick(u);
I[selectedTileNum = u] > 8 ? renderChess() : X(0, 0, 1);
}
After these little tweaks in the original code, we can proceed to our game development
Chesslings is a JavaScript game that reacts to the movements made by the underlying Tiny Chess code, as explained before.
However, we will not be talking about each and every aspect of the code. Instead, let's focus on the key features of it.
The first entry point for the Chesslings is the onTileClick
function exposed by the game
instance:
onTileClick: function (tileNum) {
},
The next entry point is the processMovement
function, responsible for acquiring and comparing the current board
state with the previous board state, so that we can find out which chess piece was moved, and which one was possibly lost in
the movement.
processMovement: function (tableArray) {
},
The board state is represented by a matrix of 10x12 positions, which contains
a smaller 8x8 chess board in its center. The reason for the matrix dimensions
being greater than a regular chess board is that only the central area of the
matrix is occupied by the chess board itself, where the margin positions are
kept so to facilitate the Tiny Chess movement calculations.
As we can see, each position in the table array is occupied by a chess piece containing a distinct
number representing the piece type, except for the pawns, the blank spaces and the off-board
positions (they have the same piece numbers). We ported the piece type codes to our game to
later show each piece according to its meaning.
Chess.PieceTypes = {
SPACE: 0,
BLACK_PAWN: 1,
BLACK_KING: 2,
BLACK_HORSE: 3,
BLACK_BISHOP: 4,
BLACK_ROOK: 5,
BLACK_QUEEN: 6,
OFF_BOARD: 7,
WHITE_PAWN: 9,
WHITE_KING: 10,
WHITE_HORSE: 11,
WHITE_BISHOP: 12,
WHITE_ROOK: 13,
WHITE_QUEEN: 14
};
As expected, the game starts with a set of 32 pieces. The code below shows how we instantiate them and
populate the array of pieces. For this we consider only the top two rows and the bottom two rows (y < 2 || y > 5)
and ignore the rest of the board. Each piece has a unique key assigned to it, along with the name of the
Spritesheet representing that category of pieces.
var keys = ('BRL,BNL,BBL,BQ,BK,BBR,BNR,BRR,BP1,BP2,BP3,BP4,BP5,BP6,BP7,BP8,' +
'WP1,WP2,WP3,WP4,WP5,WP6,WP7,WP8,WRL,WNL,WBL,WQ,WK,WBR,WNR,WRR').split(',');
for (var y = 0; y <= 7; y++) {
for (var x = 0; x <= 7; x++) {
if (y < 2 || y > 5) {
var key = keys[self.pieces.length];
var ss;
if (y < 2)
ss = window['spriteSheetS_' + key[1]];
else
ss = window['spriteSheetN_' + key[1]];
self.pieces.push(new Chess.Piece(
key,
{ x: x, y: y }, ss));
}
}
}
The names "spriteShetN" and "spriteShetS" represent both "white" and "black" teams respectively, where "N"
and "S" mean "North-facing pieces" vs. "South-facing pieces". Every category of piece ([P]awn, [R]ook, [B]ishop, k[N]ight, [Q]ueen, [K]ing)
has a "north-facing" and "south-facing" version, as we can see in the images below:
Piece | Back View | Front View |
Pawn | | |
Rook | | |
Bishop | | |
Knight | | |
Queen | | |
King | | |
Notice how all chesslings have the same colors. In order to differentiate them, we assume that the back-facing pieces are "white"
pieces while the front-facing pieces are the "black" ones. This means that when the pieces walk back, they will also walk backwards.
Note: "Chesslings" is also the name I gave to the pieces of the game. Just like "earthlings" are people living on Earth,
"chesslings" are these weird looking inhabitants of the Chess world.
Every time the underlying Tiny Chess code calls the processMovement
function, the Chesslings code compares
the state of the table array with the last processed array state. Some loops are required to check the changed positions, others
are intended to find out which pieces are affected, and then a animation is enqueued in a AnimationManager
to
process pending animations one after another. The processMovement
function structure is shown below:
processMovement: function (tableArray) {
var self = this;
if (self.lastTableArray != tableArray) {
var fromPosition;
var toPosition;
$(self.lastTableArray).each(function (index, item) {
});
if (fromPosition) {
$(tableArray).each(function (index, item) {
var row = parseInt(index / 10) - 2;
var col = (index % 10) - 1;
if (self.lastTableArray[index] == Chess.PieceTypes.SPACE &&
tableArray[index] != Chess.PieceTypes.SPACE) {
}
else if (self.lastTableArray[index] != Chess.PieceTypes.SPACE &&
tableArray[index] != Chess.PieceTypes.SPACE &&
self.lastTableArray[index] != tableArray[index]) {
}
});
$(game.pieces).each(function (index, piece) {
if (piece.isActive &&
piece.currentTile.x == fromPosition.x &&
piece.currentTile.y == fromPosition.y) {
var animation = new Chess.PointAnimation(piece, fromPosition, toPosition, 200, 1000);
animation.onFinish = function () {
$(game.pieces).each(function (index2, piece2) {
if (piece2.isActive &&
piece2.currentTile.x == toPosition.x &&
piece2.currentTile.y == toPosition.y &&
piece2.key != piece.key) {
piece2.setInactive(false);
}
});
}
self.animationManager.enqueueAnimation(animation);
}
});
}
}
self.lastTableArray = tableArray.slice();
self.processCount++;
},
Animation is a very important aspect of the code. We don't want to move the pieces from point A to point B right away. Instead, we want the piece to
move from A to B in a given T time. And this is accomplished by processing animations. Our AnimationManager
processes a queue of
instances of the PointAnimation
type, and this way we can assure that two or more animations occur concurrently.
Each instance of the PointAnimation
type is created with a set of parameters:
- piece: an instance of the
Chess.Piece
type - startPosition: the
Paper.Point
instance with the (x, y) coordinates for the animation's start position - endPosition: the
Paper.Point
instance with the (x, y) coordinates for the animation's end position - startTimeInMS: the time delay time until the animation starts, in milliseconds
- totalTimeInMS: the total animation duration in milliseconds
The main function of PointAnimation
, onFrame
, is the core of the object. This function is called many times during the
animation's lifetime. Given a deltaTimeInSec
parameter, the function calculates the relative position, taking in consideration the
total animation duration and the ellapsed time, to produce the delta vector, i.e., the D (dx, dy) vector difference between point A (ax, ay) and
point B (bx, by). The function returns true
until the total animation duration is completed.
Chess.PointAnimation = function (piece, startPosition, endPosition, startTimeInMS, totalTimeInMS) {
this.init(piece, startPosition, endPosition, startTimeInMS, totalTimeInMS)
}
$.extend(Chess.PointAnimation.prototype, {
init: function (piece, startPosition, endPosition, startTimeInMS, totalTimeInMS) {
this.piece = piece;
this.startTimeInMS = startTimeInMS;
this.startPosition = startPosition;
this.endPosition = endPosition;
this.totalTimeInMS = totalTimeInMS;
this.ellapsedTimeInMS = 0;
this.isActive = true;
this.point = startPosition;
},
onFrame: function (deltaTimeInSec) {
var self = this;
var deltaTimeInMS = deltaTimeInSec * 1000;
self.ellapsedTimeInMS += deltaTimeInMS;
if (self.ellapsedTimeInMS < self.startTimeInMS) {
return true;
}
else if (self.ellapsedTimeInMS <= self.totalTimeInMS) {
var timeFraction = self.ellapsedTimeInMS / self.totalTimeInMS;
var diffVector = new paper.Point(self.endPosition.x - self.startPosition.x, self.endPosition.y - self.startPosition.y);
self.piece.currentTile =
self.point = new paper.Point(self.startPosition.x + diffVector.x * timeFraction, self.startPosition.y + diffVector.y * timeFraction);
return true;
}
else {
self.isActive = false;
self.piece.currentTile =
self.point = self.endPosition;
consoleLog('end of: ' + self.toString());
if (self.onFinish)
self.onFinish();
return false;
}
},
toString: function () {
return 'pa (from: ' + this.startPosition + ' to:' + this.endPosition + ')';
}
});
The logic behind the animation sequencing lies in the Chess.AnimationManager
code. It is basically a queue implementation for
instances of Chess.PointAnimation
type:
Chess.AnimationId = 1;
Chess.AnimationManager = function () {
this.init();
}
$.extend(Chess.AnimationManager.prototype, {
currentAnimation: null,
queue: [],
init: function () {
this.id = Chess.AnimationId++;
},
enqueueAnimation: function (animation) {
consoleLog('enqueueing ' + animation.piece.key + ':' + animation.toString());
var self = this;
self.queue.push(animation);
},
dequeueAnimation: function () {
var self = this;
var animation = self.queue[0];
consoleLog('dequeueing ' + animation.piece.key + ':' + animation.toString());
self.queue = self.queue.slice(1, self.queue.length)
self.currentAnimation = null;
return animation;
},
getCurrentAnimation: function () {
var self = this;
if (!self.currentAnimation) {
if (self.queue.length > 0) {
self.currentAnimation = self.queue[0];
}
}
return self.currentAnimation;
}
})
Back to the processMovement
code, we notice how the instance of Chess.AnimationManager
is invoked. First
we discover which piece moved, then we enqueue an animation starting from the original table position and ending in the destination
position.
We also subscribe an onFinish
event, that will make the lost piece invisible if it´s the case, and such callback event will
take place only when the animation ends. This ensures that the processing respect the sequence of events.
processMovement: function (tableArray) {
*** HERE WE INSTANTIATE THE fromPosition AND toPosition VARIABLES ***
$(game.pieces).each(function (index, piece) {
if (piece.isActive &&
piece.currentTile.x == fromPosition.x &&
piece.currentTile.y == fromPosition.y) {
var animation = new Chess.PointAnimation(piece, fromPosition, toPosition, 200, 1000);
animation.onFinish = function () {
$(game.pieces).each(function (index2, piece2) {
if (piece2.isActive &&
piece2.currentTile.x == toPosition.x &&
piece2.currentTile.y == toPosition.y &&
piece2.key != piece.key) {
piece2.setInactive(false);
}
});
}
self.animationManager.enqueueAnimation(animation);
}
});
*** SOME POST-PROCESSING CODE HERE ***
},
As you have seen, rather than an original work, this project would not have been possible without Óscar Toledo and his amazing work with chess.
If you had the trouble to read to these final lines, thanks very much for your patience. I hope this article may be useful for you in some way,
regarding game development, isometric projection and javascript-based animation.
- 2013-08-31: Initial version.
- 2013-09-05: New mouse controls.