The Game Object
This is our main object, the “engine” of the game.
Properties
- players
- An array of Player Objects.
- current
- The index of the current player in the players array.
- board
- An array, collection of all our Square Objects.
- fences
- An array of active fences.
- canvas
- The canvas element.
- ctx
- Our canvas element’s context.
- size
- Number of rows and columns minus one (because we’re counting from zero).
- pattern
- For calculations sake, all sizes are defined in ratio of a pattern unit. This pattern unit is equal to the width (in pixels) of a square plus the width of a span (the gap between two squares).
- units
- Graphical sizes based on a pattern unit.
- span
- pawn
- square
- fence
- colors
- Well surprisingly, this is the colors used to style the game.
- square
- fence
- pawns (array)
- stroke
Constructor
The idea is to launch a new game just by setting a new Quoridor();
, so here we have our initialisation function.
I was considering an online version. You know, multiplayer and all. That’s why I went quite tight on the player’s IDs. But for us here, only two possibilities : false (the user, unlogged) and null (the artificial intelligence).
var game, anim, startAnim, pawnAnim, fenceAnim;
function Quoridor(options) {
'use strict';
// The settings and their default values
var settings = jQuery.extend(true, {
// User ID (false stands for an unlogged user)
user: false,
// Players IDs (false stands for an unlogged user, null stands for an AI)
players: [false, null],
// Number of rows and columns (have to be an even number)
size: 8,
units: {
// Span size compared to a pattern unit
span: 1 / 4,
// Pawn diameter compared to a pattern unit
pawn: 1 / 2
},
colors: { stroke: 'rgba(0,0,0,0.15)', square: '#eee', fence: '#999', pawns: ['#7a43b6', '#f89406', '#9d261d', '#049cdb'] }
}, options),
// The pawn's original positions (depending on the number of players)
bases = (settings.players.length === 2) ? [
{a: settings.size / 2, b: settings.size},
{a: settings.size / 2, b: 0}
] : [
{a: settings.size / 2, b: settings.size},
{a: 0, b: settings.size / 2},
{a: settings.size / 2, b: 0},
{a: settings.size, b: settings.size / 2}
],
i,
a,
b,
// How many fences per player, in ratio of the board size
limit = Math.floor(((Math.pow(settings.size, 2) * 5 / 16) + settings.players.length / 2) / settings.players.length);
// The game global. By doing this here, we can already access this current game object within the different classes we're about to construct
game = this;
this.size = Math.ceil(settings.size / 2.0) * 2;
// Graphical sizes based on a pattern unit (square + span = 1)
this.units = settings.units;
this.units.square = 1 - this.units.span;
// One fence is blocking two squares
this.units.fence = 2 - this.units.span;
// And let's record this "pawn" unit as a radius already
this.units.pawn = this.units.pawn / 2;
this.colors = settings.colors;
// Set the board
this.board = [];
// For all coordinates
for (a = 0; a < = this.size; a += 1) {
this.board.push([]);
for (b = 0; b <= this.size; b += 1) {
// We record a square in our "board" array
this.board[a].push(new Square(a, b));
// So for example, our square with the coordinates {a: 3, b: 7} will be game.board[3][7].
}
}
// And now that they are all constructed, we create relations between those squares : the "accesses"
for (a = 0; a <= this.size; a += 1) {
for (b = 0; b <= this.size; b += 1) {
this.board[a][b].initAccess();
}
}
// Set the players
this.players = [];
for (i = 0; i < settings.players.length; i += 1) {
switch (settings.players[i]) {
case settings.user:
// The user, "you"
this.user = new User(i, bases[i], limit);
this.players.push(this.user);
break;
case null:
// Plays against the machine
this.players.push(new Computer(i, bases[i], limit));
break;
}
}
// Set the fences
this.fences = [];
// Set the interface
this.canvas = document.getElementById('interface');
this.ctx = this.canvas.getContext('2d');
// A cheap way to set the first player in turn (could be random)
this.current = this.players.length - 1;
}
Players turn
It’s a turn-by-turn game. Every time a player plays, we first got to check if he’s won before we listen to the next one.
Quoridor.prototype.turn = function () {
'use strict';
// Set paths and accesses to make sure all data is up to date
this.validPawns();
if (this.players[this.current].win()) {
// We got a winner ! We got a winner ! We got a winner !
alert(this.players[this.current].name + ' win.');
} else {
// Next player
this.current = ((this.current + 1) < this.players.length) ? (this.current + 1) : 0;
// We render the game, and then start the new player's turn
if (this.render()) { this.players[this.current].play(); }
}
};
Set accesses, and Paths validation
The Square Object (and the Player Object) have relations between them: the accesses. Each one of them also has distances towards the different goal lines : the paths. We’ll see that in more detail with the Square Object, just understand that this function checks that we’re not isolating any pawn, and resets the accesses (and paths).
Quoridor.prototype.validPawns = function () {
'use strict';
var i;
for (i = 0; i < this.players.length; i += 1) {
// First we set the accesses
this.players[i].setAccess();
// Then the paths
if (!this.players[i].setPath()) {
// And we return false in case of a problem (isolated pawn) : we answer the test
return false;
}
}
// This is a valid move indeed
return true;
};
Get position’s Pawn, or Square
For every position on board, we either got the corresponding Square, or the Player‘s pawn that stands on it. This function returns the right object for the given coordinates.
Quoridor.prototype.getPosition = function (a, b) {
'use strict';
var i;
for (i = 0; i < game.players.length; i += 1) {
if ((game.players[i].a === a) && (game.players[i].b === b)) {
// If this position is occupied by a pawn, we return the player
return game.players[i];
}
}
// Otherwise, we return the square
return this.board[a][b];
};