Board Game Experiment, with JS Prototypes and Canvas

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];
};