Mauler: A JavaScript Framework for Abstract Strategy Games

In this post I present Mauler, a JavaScript framework for abstract strategy games. So what is an abstract strategy game? Almost all abstract strategy games conform to the strictest definition of: a game board, card, or tile game in which there is no hidden information, no non-deterministic elements, and (usually) two players or teams taking a finite number of alternating turns.

Mauler consists of three main elements:

  1. Defines Game and Player interfaces. These interfaces define the mode of interaction between games and players.
  2. Includes games (e.g. Tic-Tac-Toe) and game-playing algorithms (e.g. Alpha-beta).
  3. Includes utility functions for playing matches (e.g. play n number of games).

Here are a few games that can be implemented and played with Mauler:

The game of Tic-Tac-Toe will be used as an example to show how Mauler works. Tic-Tac-Toe was chosen because is one of the simplest, yet most popular abstract strategy board games, and most people are familiar with the rules. Here you can play the game, however, you will be playing against a perfect player (using the Alpha-Beta pruning algorithm), so the best you can expect is a draw:

Your browser does not support the canvas element.

As I mentioned before, the framework consists of two interfaces that will be described next:

  • Game
  • Player

Game

What is a game? From the game theoretical point of view a game is a description of strategic interaction that includes the constraints on the actions that the players can take and the players’ interests, but does not specify the actions that the players do take (Rasmusen, 2006). Based on this definition, we can define an interface for abstract strategy games as follows:

  • .copy() Returns a copy of the game.
  • .currentPlayer() Returns the player whose turn it is.
  • .isGameOver() Returns true if the game is over, false otherwise.
  • .move(move) Makes a move for the player whose turn it is.
  • .moves() Returns an Array of legal moves for the player in turn.
  • .newGame() Returns a new game.
  • .outcomes() Returns the outcome for each player.
  • .restart() Restarts the game.

Now here is a more detailed description of each of these methods with examples of their behavior. For simplicity, let's assume that there is a render() function that renders the game.

.copy()

Returns a copy of the game.

Example
var tic = new TicTacToe();
tic.move("5");
tic.move("9");
render(tic);
var ticCopy = tic.copy();
render(ticCopy);

.currentPlayer()

Returns the player whose turn it is.

Example

var tic = new TicTacToe();
render(tic);
tic.currentPlayer();    // "1"
tic.move("5");
render(tic);
tic.currentPlayer();    // "2"

.isGameOver()

Returns true if the game is over, false otherwise.

Example

var tic = new TicTacToe();
render(tic);
tic.isGameOver();     // false
tic.move("5");
tic.move("4");
tic.move("1");
tic.move("9");
tic.move("3");
tic.move("2");
tic.move("7");
render(tic);
tic.isGameOver();    // true

.move(move)

Makes a move for the player whose turn it is.

Example:
var tic = new TicTacToe();
render(tic);
tic.move("5");
render(tic);

.moves()

Returns an Array of legal moves for the player in turn.

Example:
var tic = new TicTacToe();
render(tic);
tic.moves();    // ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
tic.move("5");
tic.move("4");
tic.move("1");
render(tic);
tic.moves();    // ["2", "3", "6", "7", "8", "9"]
tic.move("9");
tic.move("3");
tic.move("2");
tic.move("7");
render(tic);
tic.moves();    // []

.newGame()

Returns a new game.

Example:
var tic = new TicTacToe();
tic.move("5");
tic.move("9");
render(tic);
var newGame = tic.newGame();
render(newGame);
render(tic);

.outcomes()

Returns a dictionary with the outcomes for each of the players.

var tic = new TicTacToe();
render(tic);
tic.move("5");
tic.move("4");
tic.move("1");
tic.move("9");
tic.move("3");
tic.move("2");
tic.move("7");
render(tic);
tic.isGameOver();    // true
tic.outcomes();      // { "1": "WIN", "2": "LOSS" }

.restart()

Restarts the game.

Example

var tic = new TicTacToe();
tic.move("5");
tic.move("9");
render(tic);
tic.restart();
render(tic);

Player

Players are the individuals who make decisions (take moves). We can define an interface for a player as simple as this:
function player(game) {
    return "Move 1";
}
A player is asked for the move to be taken and is provided with the current state of the game. The player can analyze the current state of the game (e.g. using a search algorithm) and return the move to be taken.

Random Player

We can easily create a uniformly random player that can be used in games that follow the Game interface as follows:
function randomPlayer(game) {
    return Math.floor(Math.random() * game.moves().length);
}

Play a Match

Now that we have described the Game and Player interfaces we can use them to play a full match of Tic-Tac-Toe:
var tic = new TicTacToe();
render(tic);
while (!tic.isGameOver()) {
    tic.move(randomPlayer(tic));
}

Source Code

You can get the source code of Mauler on Github.

Summary

Mauler is a very simple, yet concise enough framework to describe games and game-playing algorithms. This framework will be used in following blog posts to describe algorithms such as Minimax, Alpha-Beta pruning and Monte-Carlo Tree Search.


comments powered by Disqus