Connect 4 is a turn-based two-player game, where each player would drop a chip down a column, with the objective to get four chip of the same color in a row. It can be vertical, horizontal, or diagonal.
In this chapter, we will build Connect4-as-a-Service. An API that allows you to build a game of Connect 4 on any client, be it a website, mobile app, or just play it from the command line; why not?
In Chapter 1, Building a Basic Express Site, and Chapter 2, MMO Word Game, we covered the most generic use cases for an Express backed API, which is to serve and persist data to and from a database. In this chapter, we'll cover something more fun. We'll build a multiplayer game API!
Some topics that will be covered include authentication, game state modeling, and validation middleware. Also, we will build an app using test-driven development with maximum code coverage.
For your reference, this is the folder structure of our app, which we will build throughout the chapter:
How do you create a game? How do you join a game? How do you make a move? And how do you persist the game state in a DB?
It is always a good idea to start with the data structure. So let's get to it!
We will represent the board as a 2-dimensional array, with the values being 'x'
, 'o'
, or ' '
, representing the three possible states for each location on the grid. Here's an example, where player 2 wins the game:
This game state would be represented in an array as follows:
[ [' ',' ',' ',' ',' ',' ',' ',' '], [' ',' ',' ',' ',' ',' ',' ',' '], [' ','o',' ',' ',' ',' ',' ',' '], [' ','x','o','o','o',' ',' ',' '], [' ','x','x','o','x',' ',' ',' '], ['o','x','x','x','o',' ',' ',' '] ]
This would suffice if the game were to be played locally with the state being stored in memory. In our case, we want to play on the internet, so we will need a way to identify which game we are playing, as well as which player you are, and whose turn it is. A game document would look as follows:
{ boardId: '<id>', p1Key: '<p1key>', p1Name: 'express', p2Key: '<p2key>', p2Name: 'koa', columns: 7, rows: 6, status: 'Game in progress', winner: undefined, turn: 1, board: [...] }
Here are the parameters:
Parameter |
Description |
---|---|
|
This is a unique ID that you'll need if you want to take a look at the current game state. |
|
This is a secret token to identify player 1; we want to avoid the possibility of cheating of course |
|
This is player 1's name |
|
This is a secret token to identify player 2 |
|
This is a player 2's name |
|
This is the total number of turns played on this board |
|
This is the number of rows of the game board |
|
This is the number of columns of the game board |
|
This is the game state stored in a 2D array |
|
This is either Game in progress or Game Over. |
|
This is the name of the winner once the game is over |
Let's use the same app folder structure as was introduced in Chapter 2, Building a Basic Express Site, and let's define the preceding as a Mongoose model in src/models/game.js
:
var mongoose = require('mongoose'), var gameSchema = new mongoose.Schema({ type: String, required: true }, p2Key: { type: String, required: true }, p1Name: { type: String, required: true }, p2Name: { type: String }, turn: { type: Number, required: true }, boardId: { type: String, required: true, index: { unique: true } }, board: { type: Array, required: true }, rows: { type: Number, required: true }, columns: { type: Number, required: true }, status: { type: String }, winner: { type: String } }); module.exports = mongoose.model('Game', gameSchema);