Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Chesslings

4.99/5 (43 votes)
9 Sep 2013CPOL9 min read 77.4K   1.1K  
An isometric chess game using Paper JS

Image 1

Table of Contents

Introduction

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.

System Requirements

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.

Paper JS

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.

Toledo's Tiny Chess

Image 2

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:

JavaScript
//(c)2009 Oscar Toledo G.
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":

HTML
<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.

JavaScript
    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

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:

JavaScript
onTileClick: function (tileNum) {
    //here goes the code that highlights the selected tile.
},

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.

JavaScript
processMovement: function (tableArray) {
    //here goes the code that processes the game board's current state.
},

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.

Image 3

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.

JavaScript
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.

JavaScript
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

Image 4

Image 5

Rook

Image 6

Image 7

Bishop

Image 8

Image 9

Knight

Image 10

Image 11

Queen

Image 12

Image 13

King

Image 14

Image 15

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.

Movement Detection

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:

JavaScript
processMovement: function (tableArray) {
    var self = this;

    //Let's proceed only when something changed
    if (self.lastTableArray != tableArray) {
        var fromPosition; //the position from where some piece moved
        var toPosition; //the position to where the piece moved
        $(self.lastTableArray).each(function (index, item) {

                //some code to set the "fromPosition" variable

        });

        //Let's proceed only if some movement was detected
        if (fromPosition) {
            $(tableArray).each(function (index, item) {
                var row = parseInt(index / 10) - 2;
                var col = (index % 10) - 1;

                //If this position was last time empty but now is occupied by a piece,
                //then the piece moved to here
                if (self.lastTableArray[index] == Chess.PieceTypes.SPACE &&
                    tableArray[index] != Chess.PieceTypes.SPACE) {

                    //some code to set the "toPosition" variable

                }
                //If this position was last time occupied by a piece but now is replaced by another one,
                //then this last piece was lost
                else if (self.lastTableArray[index] != Chess.PieceTypes.SPACE &&
                    tableArray[index] != Chess.PieceTypes.SPACE &&
                    self.lastTableArray[index] != tableArray[index]) {

                    //some code to set the "toPosition" variable

                }
            });

            //This loop will decide which piece moved, and take action accordingly
            $(game.pieces).each(function (index, piece) {
                if (piece.isActive &&
                    piece.currentTile.x == fromPosition.x &&
                    piece.currentTile.y == fromPosition.y) {

                    //The animation will move "piece" from "fromPosition" to "toPosition" in 1000 milliseconds,
                    //starting 200 milliseconds after the start of the animation
                    var animation = new Chess.PointAnimation(piece, fromPosition, toPosition, 200, 1000);
                    animation.onFinish = function () {

                        //When the animation ends, we inactivate (make invisible) the piece that
                        //may have been lost to the attacking one.
                        $(game.pieces).each(function (index2, piece2) {
                            if (piece2.isActive &&
                                piece2.currentTile.x == toPosition.x &&
                                piece2.currentTile.y == toPosition.y &&
                                piece2.key != piece.key) {

                                //setting a chess piece to "inactive" actually makes it invisible
                                piece2.setInactive(false);
                            }
                        });
                    }
                    //we enqueue the animation, so that it will be processed only when required
                    self.animationManager.enqueueAnimation(animation);
                }
            });

        }

    }

    self.lastTableArray = tableArray.slice();
    self.processCount++;
},

Animation

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.

JavaScript
//********************
//Chess.PointAnimation
//********************

//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 animation duration in milliseconds
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;

        //the delta time, in seconds
        var deltaTimeInMS = deltaTimeInSec * 1000;

        //adds to the total ellapsed time for this animation instance
        self.ellapsedTimeInMS += deltaTimeInMS;

        if (self.ellapsedTimeInMS < self.startTimeInMS) {
            //the animation is active but still not started, so do nothing
            return true;
        }
        else if (self.ellapsedTimeInMS <= self.totalTimeInMS) {
            //the current point is calculated taking in consideration the total animation duration, the ellapsed time,
            //the start and the end position.
            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 {
            //when the total animation duration is completed, we force the piece to assume the end position.
            //the onFinish callback function is invoked when needed.
            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:

JavaScript
//********************
//Chess.AnimationManager
//********************
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;
        //positioning the animation at the end of the queue
        self.queue.push(animation);
    },
    dequeueAnimation: function () {
        var self = this;
        //taking the first element of the queue
        var animation = self.queue[0];
        consoleLog('dequeueing ' + animation.piece.key + ':' + animation.toString());
        //deleting the first element
        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.

JavaScript
processMovement: function (tableArray) {

    *** HERE WE INSTANTIATE THE fromPosition AND toPosition VARIABLES ***

    //This loop will decide which piece moved, and take action accordingly
    $(game.pieces).each(function (index, piece) {
        if (piece.isActive &&
            piece.currentTile.x == fromPosition.x &&
            piece.currentTile.y == fromPosition.y) {

            //The animation will move "piece" from "fromPosition" to "toPosition" in 1000 milliseconds,
            //starting 200 milliseconds after the start of the animation
            var animation = new Chess.PointAnimation(piece, fromPosition, toPosition, 200, 1000);
            animation.onFinish = function () {

                //When the animation ends, we inactivate (make invisible) the piece that
                //may have been lost to the attacking one.
                $(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);
                    }
                });
            }
            //we enqueue the animation, so that it will be processed only when required
            self.animationManager.enqueueAnimation(animation);
        }
    });

    *** SOME POST-PROCESSING CODE HERE ***
},

Final Considerations

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.

History

  • 2013-08-31: Initial version.
  • 2013-09-05: New mouse controls.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)