All Projects → jiahaog → ttt

jiahaog / ttt

Licence: MIT License
A Tic-Tac-Toe game with React and an unbeatable AI

Programming Languages

javascript
184084 projects - #8 most used programming language
HTML
75241 projects
CSS
56736 projects

T T T Build Status

image

A Tic-Tac-Toe game with React and an unbeatable AI.

Play it here!

Introduction

Even though this might seem like a simple project, I was carried away and ended up making it way more comprehensive than I initially wanted it to be. Initially, I just wanted to make a simple AI for Tic-Tac-Toe after reading this blog post, but it led to me experimenting with ES6, Web Workers, React, Gulp and even headaches such as making the game mobile responsive in the across browsers and devices.

Structure

The core game logic is written as a Node.js module with the endpoint at ./src/js/game/index.js, which exposes the necessary objects that should be used on the client side, such as the Game Board, and the Player classes.

As the Javascript code is written in ES6, Babel is used to compile them into ES5 Javascript, in either ./lib for Node.js development, or ./dist with Gulp for the Web.

These game module contains classes coded such that only key functions are exposed to the user on a high level. For example, we can use these objects to implement a command line version of Tic-Tac-Toe, in ./src/js/game/implementation/.

Because of this, the game can be set up concisely with callbacks in ./src/js/main.js, with a simplified version as follows:

import gameApi from './src/js/game/index.js';
const game = new gameApi.TicTacToe();
const aiPlayer = new gameApi.players.PerfectPlayer(game, 'AI-Player');
const clientPlayer = new gameApi.playersPlayer(game, 'You');

clientPlayer.onBoardChange(grid => {
    // updateView(grid);
});

clientPlayer.onMyTurn(() => {
    // makeGridClickable();
});

clientPlayer.onGameOver(winnerData => {
    // showWinner(winnerData);
});

game.registerPlayers(aiPlayer, clientPlayer);
game.newGame();

React acts as a view for the game which aids in rendering the correct DOM elements depending on it's state.

Finally, all that needs to be done is to Browserify main.js with Babel as a plugin, and all the required modules will be compiled for web use. A Gulp task is defined for this, for an automated workflow.

React

This is my first time using React, and I found it extremely helpful in managing the state of the game in a controlled manner. For example, I want apply a game-disabled CSS class to the rendered <div> when it is not the client player's turn. Instead of having to set this class directly, React allows me to make the view depend on the component state. A small except would be as follows:

class Game extends React.Component {
  newGame = () => {
    // set up game
    // ...

    clientPlayer.onMyTurn(() => {
      this.setState({ myTurn: true });
    });
  }
  // helper function to get the css class
  gridCss = () => {
    if (this.state.myTurn) {
      return 'game-enabled';
    } else {
      return 'game-disabled';
    }
  }
  render() {
    // apply reactive css class
    return <div className={this.gridCss()}>
      // ...
    <div>
  }
}

When the callback for clientPlayer.onMyTurn() is executed, the game.state.MyTurn variable is mutated, which implicitly triggers a new render of the result with a different CSS class attribute.

I highly recommend this introduction to React.

Babel

Three plugins are used:

es2015

To compile the ES6 syntax used

react

To compile Javascript for React written in JSX

stage-0

To simplify creation of React classes with ES6 classes.

In ES6:

class ExampleButton extends React.component {
  onClick() {
    // do things
  }
  render() {
    return (
      <div onClick={this.onClick.bind(this)}>Click Me!</div>
    );
  }
}

With ES7 property initializers in stage-0, we don't have to explicitly bind our onClick method to this.

class ExampleButton extends React.component {
  onClick = () => {
    // do things
  }
  render() {
    return (
      <div onClick={this.onClick}>Click Me!</div>
    );
  }
}

Read more about React with ES6.

Web Workers

When I finished implementing the game for the browser, I realized that on the first few turns made, the AI takes a few seconds to process the move that needs to be made. Normally, this would not be a problem, but the synchronous implementation of the Minimax algorithm ended up blocking the entire browser while the move is computed. Thus, I used Web Workers to compute the next move on a separate thread, so that the user experience of the game is preserved.

Usage

Installation

Dependencies

You need Gulp installed globally to build the webpage.

# Install Gulp
$ npm install --global gulp

Building

$ git clone https://github.com/jiahaog/ttt.git
$ cd ttt

# Install dependencies
$ npm install

# Build compiled game to `./dist/index.html`
$ gulp build

Development

Game Module

We can test the game modules separately from the browser, with these commands below.

The compiled module files are built to ./lib for development purposes.

Building

# Do this before running any files in `./lib`
$ npm run build

Other Commands

# watch and build es6 source files in `./src` to `./lib`
$ npm run watch

# run compiled files, etc.
$ node lib/js/game/implementation/vsAi.js

# Run two player cli game
$ npm run two-player

# Run vs AI cli game
$ npm run vs-ai

Tests

Tests in ./test for the game module are written in Mocha, and can be executed using the following command.

$ npm test

Webpage development

The gulpfile has been set up with various tasks to compile and Browserify all the Javascript modules into ./dist, and the final game can be viewed by opening ./dist/index.html after gulp build has been completed.

Commands

# build files to `./dist`
$ gulp build

# Watch files with browserSync
$ gulp watch

# deployment to GitHub Pages
$ gulp deploy
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].