The Player Object
Constructor
As explained earlier, our Player object extends a Square object. I could have called it “Pawn”… maybe.
function Player(i, base, limit) {
'use strict';
// Index of this player in the game.players array
this.i = i;
// Call the parent Square constructor
Square.call(this, base.a, base.b);
this.setAccess();
// How many fences this player got left
this.limit = limit;
// Which line is this player's goal
this.goal = (base.a === 0) ? 'right' : (base.a === game.size) ? 'left' : (base.b === 0) ? 'down' : 'up';
// Add the player's div to the interface
this.caption = jQuery('<div id="player-' + i + '" class="player goal-' + this.goal + '" style="border-color:' + game.colors.pawns[i] + '"><span class="name">' + this.name + '</span><span style="color:' + game.colors.pawns[i] + '" class="fences pull-right"></span></div>');
jQuery('#players').append(this.caption);
this.displayFences();
}
// Inherit Square
Player.prototype = Object.create(Square.prototype);
Accesses and Paths
When a player is moving to a new position on the board, we need to update its accesses with .setAccesses().
If no path is found .setPath() returns false, so we can make sure that an access to the goal line is always left open.
These functions will be called for tests as well.
Player.prototype.setAccess = function () {
'use strict';
// We get the accesses from the Square this Player is on
this.access = game.board[this.a][this.b].access;
// And we set this Player as the access of the neighbouring positions (if they're valid, !== false)
if (this.access.up) { this.access.up.access.down = this; }
if (this.access.right) { this.access.right.access.left = this; }
if (this.access.down) { this.access.down.access.up = this; }
if (this.access.left) { this.access.left.access.right = this; }
// Make the callbacks easy
return true;
};
Player.prototype.setPath = function () {
'use strict';
var i, j;
// We run the pathFinder for this player's goal and from its current position
this.pathFinder(this.goal, false);
// We reset every Square's and Player's tokens to false (before any new pathFinder() call)
for (i = 0; i <= game.size; i += 1) { for (j = 0; j <= game.size; j += 1) { game.board[i][j].token = false; } }
for (i = 0; i < game.players.length; i += 1) { game.players[i].token = false; }
// We apply this player's paths to its position
game.board[this.a][this.b].paths = this.paths;
// And we return what we set
return this.paths[this.goal];
};
The getter for the accesses actually return the access with the shortest path, considering a potential jump over a neighbouring pawn.
Player.prototype.getAccess = function (jump, goal) {
'use strict';
var direction, square, move = false;
// Do we get the access as a neighbouring pawn ? Is the jumping allowed ?
if (jump === undefined) { jump = true; goal = this.goal; }
for (direction in this.access) {
// For each valid access
if (this.access.hasOwnProperty(direction) && this.access[direction]) {
// Get either that Square object, either a one from that Player object
if (!(this.access[direction] instanceof Player)) {
square = this.access[direction];
} else if (jump) {
// If this access is a neighbouring pawn and we can jump it : we call the access from it (disabling jump).
square = this.access[direction].getAccess(false, goal);
} else {
// You can't jump more than one pawn
square = false;
}
// Is that square our smartest move so far ? Oh, and if a jump, is it a valid one ?
if (square && (!move || (square.paths[goal] < move.paths[goal]) || ((square.paths[goal] === move.paths[goal]) && (Math.random() < 0.5))) && (!jump || !this.access[direction].access[direction] || (this.access[direction].access[direction] instanceof Player) || (this.a === square.a) || (this.b === square.b))) {
// Yep, let's set it up.
move = square;
}
}
}
// This now must be the smartest move.
return move;
};
Player.prototype.getPath = function () {
'use strict';
// Basically return this player's path
return this.paths[this.goal];
};
Access validation
Here we check if the given Square Object parameter is either one of our valid accesses, or one of our neighbour’s ones.
The game.rule property is here to record which rule we should remind the player of in the “alert” part of the interface. Mostly in case of an error, but not necessarily.
Player.prototype.validSquare = function (square) {
'use strict';
// "a" and "b" here are the difference between our pawn's coordinates and our destination's ones
var a = Math.abs(square.a - this.a),
b = Math.abs(square.b - this.b),
move,
opp;
for (move in this.access) {
if (this.access.hasOwnProperty(move) && this.access[move]) {
if ((this.access[move].a === square.a) && (this.access[move].b === square.b)) {
// Our destination is recorded in this player's valid accesses !
if (this.access[move] instanceof Player) {
// #rule-6 : When two pawns face each other on neighbouring squares which are not separated by a fence, the player whose turn it is can jump the opponent's pawn (and place himself behind him), thus advancing an extra square.
game.rule = 6;
return false;
}
// Our destination is a valid Square object...
return true;
}
if (this.access[move] instanceof Player) {
// This player has access to another Pawn : he has a neighbour.
for (opp in this.access[move].access) {
// And if this neighbour has access to our destination, is it a valid one ?
if (this.access[move].access.hasOwnProperty(opp) && this.access[move].access[opp] && (this.access[move].access[opp].a === square.a) && (this.access[move].access[opp].b === square.b)) {
// rule-8 : It is forbidden to jump more than one pawn.
if (this.access[move].access[opp] instanceof Player) { game.rule = 8; return false; }
// #rule-7 : If there is a fence behind the said pawn, the player can place his pawn to the left or the right of the other pawn.
if (this.access[move].access[move] && !(this.access[move].access[move] instanceof Player) && (move !== opp)) { game.rule = 7; return false; }
return true;
}
}
}
}
}
// #rule-2 : The pawns are moved one square at a time, horizontally or vertically, forwards or backwards.
// #rule-3 : The pawns must get around the fences.
game.rule = (((a === 0) && (b === 1)) || ((b === 0) && (a === 1))) ? 3 : 2;
return false;
};
Actions execution
We have two possible actions: moving the player’s pawn, or putting up a fence.
Both .move() and .putUp() are actually firing requestAnimationFrame, and it’s only when the animation is completed that our action is recorded.
Player.prototype.move = function (square) {
'use strict';
// We reset the accesses for the Square we're leaving
if (this.access.up) { this.access.up.access.down = game.board[this.a][this.b]; }
if (this.access.right) { this.access.right.access.left = game.board[this.a][this.b]; }
if (this.access.down) { this.access.down.access.up = game.board[this.a][this.b]; }
if (this.access.left) { this.access.left.access.right = game.board[this.a][this.b]; }
// We set this "pawnAnim" global to our destination position
pawnAnim = square;
// We reset the "startAnim" global
startAnim = false;
// And we call the requestAnimationFrame to proceed this move
anim = requestAnimationFrame(this.animPawn);
};
Player.prototype.putUp = function (fence) {
'use strict';
// Set the "fenceAnim" global to the fence we want to put up.
fenceAnim = fence;
// Reset the "startAnim" global
startAnim = false;
// And we call the requestAnimationFrame to proceed this move
anim = requestAnimationFrame(this.animFence);
};
Player.prototype.animPawn = function () {
'use strict';
// "this" is getting out of this method's context, so we need to redefine the current player here
var progress, player = game.players[game.current];
// If this is the first call, we set the starting time
if (!startAnim) { startAnim = Date.now(); }
// Calculating the progress (the animation duration is 400ms)
progress = (Date.now() - startAnim) / 400;
// Set the new coordinates of our pawn (depending on the progress)
player.a = player.a + (pawnAnim.a - player.a) * progress;
player.b = player.b + (pawnAnim.b - player.b) * progress;
// Render the interface.
game.render();
if (progress < 1) {
// If the animation is not completed, we request another Animation Frame
requestAnimationFrame(player.animPawn);
} else {
// Otherwise, we cancel the animation
cancelAnimationFrame(anim);
// We make sure our pawn exactly arrived to its destination
player.a = pawnAnim.a;
player.b = pawnAnim.b;
// Set the pawn's accesses for this position
player.setAccess();
// Get the new paths values (which are the same as our new position)
player.paths = game.board[player.a][player.b].paths;
// And it's now the next player's turn !
game.turn();
}
};
Player.prototype.animFence = function () {
'use strict';
// "this" is getting out of this method's context, so we need to redefine the current player here
var progress, player = game.players[game.current];
// If this is the first call, we set the starting time
if (!startAnim) { startAnim = Date.now(); }
// Calculating the progress (the animation duration is 400ms)
progress = (Date.now() - startAnim) / 400;
// Render the interface.
game.render(fenceAnim, progress);
if (progress < 1) {
// If the animation is not completed, we request another Animation Frame
requestAnimationFrame(player.animFence);
} else {
// Otherwise, we cancel the animation
cancelAnimationFrame(anim);
// The player loses one of his fences
player.limit -= 1;
// Add this fence to game.fences
game.fences.push(fenceAnim);
// Close the accesses of the concerned objects
game.closeAccess(fenceAnim);
// Update the fences counter
player.displayFences();
// And it's now the next player's turn !
game.turn();
}
};
Utilities
Now we need a function to tell us if the player is winning. Too easy.
Player.prototype.win = function () {
'use strict';
switch (this.goal) {
// Is the player now on his goal line ?
case 'up':
return (this.b === 0);
case 'right':
return (this.a === game.size);
case 'down':
return (this.b === game.size);
case 'left':
return (this.a === 0);
}
};
And here, a little function to update the “fences left” indicator in this player’s caption div.
Player.prototype.displayFences = function () {
'use strict';
var display = '', i;
for (i = 0; i < this.limit; i += 1) { display += '|'; }
// Display the fences left in this player's caption
jQuery('.fences', this.caption).text(display);
};