Building online multiplayers games with Meteor [codes + tutorials] - 2

This is the second post of a blog series. Please refer to the previous post for the series introduction.

Source Code

Source code at the end of this part can be found at:

Synchronizing data (game) between server and clients

As mentioned in the previous post, we will be building an online Tic-Tac-Toe in Meteor. In this section, we will be focusing on the data synchronization between clients and server to demonstrate the power of reactivity in Meteor.

We will start the section by setting up a Meteor project with React.

Setting up a Meteor app with React
Install Meteor

This project is built with Meteor 1.4.

The first step is to install Meteor on machine (if you haven't done so before). For OSX/Linux, you can execute the following command in terminal:

$ curl https://install.meteor.com/ | sh

and for Windows, you can download the installer here: https://www.meteor.com/install

Create Meteor project

In your terminal, execute

$ meteor create my-game-site

Then run your project:

$ cd my-game-site
$ meteor npm install
$ meteor

now, check that your meteor app is running at http://localhost:3000

Also we will be using React for frontend instead of the default Blaze template. Execute the following commands to install the relevant React packages:

$ meteor npm install --save react react-dom
$ meteor npm install --save react-addons-pure-render-mixin
$ meteor add react-meteor-data
Some cleanup

Please make the following updates to remove the default dummy content and get the React rendering ready:

a) remove ./client/main.js

b) update ./client/main.html with the following content:

<head>  
  <title>my-game-site</title>
</head>  
<body>  
  <div id="render-target"></div>
</body>  

We have cleared the dummy content, and create a single <div> element, in which our content will be rendered into.

c) create ./client/main.jsx with the following content:

import React from 'react';  
import { Meteor } from 'meteor/meteor';  
import { render } from 'react-dom';  
import App from '../imports/ui/App.jsx';

Meteor.startup(() => {  
  render(<App />, document.getElementById('render-target'));
});

and create imports/ui/App.jsx with the following content:

import React, { Component } from 'react';

// App component - represents the whole app
export default class App extends Component {  
  render() {
    return (
      <div>[Game Content]</div>
    )
  }
}

Just a quick note. Meteor will load ALL files outside imports/ directory on startup. Therefore, ./client/main.jsx will be loaded automatically while ./imports/ui/App.jsx will not. Besides, the code inside Meteor.startup() will be executed as soon as DOM is ready.

What happens in the above code is that: the React component <App> will be rendered inside the render-target DOM element declared in main.html. For the moment, all you need to know about React is that the render() function in the App component is responsible for producing the actual content. Obviously, we are going to update this function later.

If everything is done correctly, you should see the following page at http://localhost:3000

Building our first game

Now, we are ready for the real stuff!

The first thing we need is data store, and the way Meteor handles data is through Collections. If you are familiar with NoSQL database like mongoDB, you should be familiar with this term. Otherwise, if you come from the background of relational database, consider it as Table-like structure.

i) Create Games Collection
Create a file ./imports/api/collections/games.js with the following content:

import { Mongo } from 'meteor/mongo';

export default Games = new Mongo.Collection('games');  

What we did is create a Collection named Games, which is backed by a Mongo database Collection named games.

ii) Instantiate a game document on server startup

Then, replace ./server/main.js with the following content:

import { Meteor } from 'meteor/meteor';  
import Games from '../imports/api/collections/games.js'; // import Games collection

Meteor.startup(() => {  
  // code to run on server at startup
  Games.remove({});  // remove all existing game documents

  let gameDoc = {        
    board: [[null, null, null], [null, null, null], [null, null, null]]
  };                     

  Games.insert(gameDoc); // insert a new game document into the collection
});

Now, on server startup, we will remove all existing game documents, and then create one and only one game document.

Noted that the above code is run ONLY on the server side. In Meteor, everything under server/ directory is run only on server while everything under client/ directory is run only on the client. Everything else is run in both.

iii) Sending the game document to client

Next comes the interesting part. We will see how the game that is created on server get sent to the clients. Open ./imports/ui/App.jsx file again and make the following updates:

import React, { Component } from 'react';  
import { createContainer } from 'meteor/react-meteor-data';  
import Games from '../api/collections/games.js';  
import GameBoard from './GameBoard.jsx';

class App extends Component {  
  render() {
    return (
      <div>
        {this.props.game?
          <GameBoard game={this.props.game}/>
        : null}
      </div>
    )   
  }
}

export default createContainer(() => {  
  return {
    game: Games.findOne()
  };  
}, App);

The magic happens on the following snippet:

export default createContainer(() => {  
  return {
    game: Games.findOne()
  };  
}, App);

Consider createContainer as the Meteor way for making the React component reactive. It's ok if you don't understand what that means. For the moment, just assume there is some magic here. Games.findOne() will fetch a single document in the Games Collection. This document is magically pushed to the <App> component, which can be accessed by this.props.game. Inside the <App> render function, we further pass this game into this sub-component <GameBoard>, which will be created in a moment.

Believe it or not. With simple code like this, the game document we created on the server has already been synchronized to the clients! You will see the effect very soon. Stay tuned for little bit more!

iv) Displaying the game
Apparently, we need to create another React Component <GameBoard> to display the game. Let's create a file ./imports/ui/GameBoard.jsx with the following content:

import React, { Component } from 'react';

export default class GameBoard extends Component {  
  currentPlayer() {
    // determine the current player by counting the filled cells
    // if even, then it's first player, otherwise it's second player
    let filledCount = 0;
    for (let r = 0; r < 3; r++) {
      for (let c = 0; c < 3; c++) {
        if (this.props.game.board[r][c] !== null) filledCount++;
      }
    }
    return (filledCount % 2 === 0? 0: 1);
  }

  handleCellClick(row, col) {
    let currentPlayer = this.currentPlayer();
    let game = this.props.game;
    game.board[row][col] = currentPlayer;
    Games.update(game._id, {
      $set: {board: game.board}
    });
  }

  renderCell(row, col) {
    let value = this.props.game.board[row][col];
    if (value === 0) return (<td>O</td>);
    if (value === 1) return (<td>X</td>);
    if (value === null) return (
      <td onClick={this.handleCellClick.bind(this, row, col)}></td>
    );
  }
  render() {
    return (
      <table className="game-board">
        <tbody>
          <tr>
            {this.renderCell(0, 0)}
            {this.renderCell(0, 1)}
            {this.renderCell(0, 2)}
          </tr>
          <tr>
            {this.renderCell(1, 0)}
            {this.renderCell(1, 1)}
            {this.renderCell(1, 2)}
          </tr>
          <tr>
            {this.renderCell(2, 0)}
            {this.renderCell(2, 1)}
            {this.renderCell(2, 2)}
          </tr>
        </tbody>
      </table>
    )
  }
}

Again, the render() function is responsible for producing the actual content. In this case, it's building a 3x3 tables (which is also the board of the Tic-Tac-Toe game).

We also added click handler on the table cells, i.e.

  handleCellClick(row, col) {
    let currentPlayer = this.currentPlayer();
    let game = this.props.game;
    game.board[row][col] = currentPlayer;
    Games.update(game._id, {
      $set: {board: game.board}
    });
  }

So currentPlayer is alternating between 0 and 1, corresponding to the marks "O" and "X". We put the current player mark on the board cell when it is clicked. Games.update is a Collections method call to update the document. The syntax is very similar to mongdb. Yes, we are doing “Games.update()” simply on the client side!

We are almost done. Before we check the result, let's add some css style by replacing the content of ./client/main.css with the following:

table.game-board tr td {  
  border: 1px solid black;
  width: 30px;
  height: 30px;
  text-align: center;
}
Result

Now restart your server, and open two browsers (or two tabs) at http://localhost:3000. You should be able to see a 3 x 3 game board like below.

If you click on any cell, it will get filled with "O" or "X". More importantly, it get synchronized to another browser immediately.

The game data is also synchronized with the server, which is backed by a mongoDB. To see it, open another terminal and start the meteor mongo shell with the following command:

$ meteor mongo

a Mongo shell should have started, then execute the following command to retrieve the games collection.

meteor:PRIMARY> db.games.find()  

and you should see result like this:

{ "_id" : "yHoMFSppcdMQCSmM3", "board" : [ [ null, null, null ], [ null, 1, null ], [ 2, null, null ] ] }

Look, with just a few lines of code, the data is synchronized between clients and server in this seamless way!

Conclusion

That concludes our first exercise. Obviously, there is a lot missing before the game is playable. Our current version only allows a single game instance created on server startup. In the next section, we will extend this to allow multiple games be created on the client side by users. In the other words, we will create a simple game lobby.

Next post:

http://blog.hiukim.com/building-online-multiplayers-games-with-meteor-codes-tutorials-3