Skip to content

Creating a New Variant

Benjamin Jones edited this page Apr 26, 2024 · 5 revisions

Warning: THIS PAGE IS A WORK IN PROGRESS


This guide is for creating a new variant from scratch. Often it can be quicker to inherit from or copy an existing variant, but this is a good exercise to understand the interface between a variant and the framework. If you'd prefer to look at code, please check out example PR (TODO: add PR), which implements Tic-Tac-Toe.

Step 0: Set up your development environment

Check out the README for instructions on how to set up the development environment. At a high level, this involves:

  • Downloading the source with git
  • Pulling in dependencies with yarn
  • Installing MongoDB

Step 1: Generate boilerplate for the new variant

We will use the new-variant script in shared:

yarn workspace @ogfcommunity/variants-shared run new-variant

This will generate two files:

  • shared/src/variants/your_variant.ts
    • This is the code file where you will implement all the logic for your game.
  • shared/src/variants/__tests__/your_variant.test.ts
    • This is where you can add tests for the variant. Adding tests can be a good way to verify that your code works as expected, and it can help prevent breakages in the future.

Step 2: Set up game in the constructor

  // Initialize state of the game for a given config
  constructor(config?: object) {
    // AbstractGame will set this.config to the defaultConfig() if config is undefined.
    // Therefore, we access config through this.config in the rest of this function
    super(config);

    // Validate the config before trying to build an object.

    // Setup the empty board state and other variables
    this.board = new Grid<Color>(this.config.width, this.config.height).fill(null);
  }

Step 3: Define the player ordering

Variants may have a variety of player orderings. For example, Chess and Go have 2 players who alternate turns. In contrast, F-Go allows both players to play at the same time. Other variants allow for more than two players.

In the govariants codebase, it's possible to customize these behaviors using a few different functions.

numPlayers() specifies the number of players that are allowed to join the game. This number should not change throught the course of the game.

override numPlayers(): number {
  return 2;
}

nextToPlay() function that returns an Array of players who may submit a move in the current round.

override nextToPlay(): number[] {
  // Return empty array if game is over.
  // You can check the phase property of AbstractGame
  if (this.phase === "gameover") {
    return [];
  }

  return [this.next_to_play];
}

In playMove(), you can run some validation on the player, and throw an Error if the incorrect player has moved. You will also need to update state related to the player order.

// Validate move and update the state accordingly
override playMove(player: number, move: string): void {
  // ...resignation and timeout handling...

  // Validate the current player by comparing to a member variable
  // that is tracking the active player
  if (player !== this.next_to_play) {
    throw new Error(`Not ${player}'s turn!`);
  }

  // ...process move...

  // Switch players
  this.next_to_play = player === 0 ? 1 : 0;

  // It is important to increase the round in order for game playback
  // and timers to work
  super.increaseRound();
}