Building CLI Tic Tac Toe Game with Node JS

Building CLI Tic Tac Toe Game with Node JS

It will be fun! Also help you to build up some programming basic like Class, Function, Loop etc.

ยท

7 min read

Introduction

One thing that makes developers other than ordinary people out there is a command line. The moment someone opens up the terminal and types out some commands, they instantly transform into a hacker (at least they seem to be hackers). It makes me want to build it myself.

Why Tic Tac Toe, you think? Well, it's simple enough to build but requires user input to make a game playable.

The tutorial is divided into three different sections. In the first section, you will learn how to set the environment to build a CLI App. After that, we develop our tic tac toe platform. Finally, we will publish it to NPM and see if we can play it on our terminal.

And our goal is to make a playable two-player Tic Tac Toe game from CLI. You can try the final result by executing this command.

npx @alvinend/tic-tac-toe-cli

Setting Up Environment

Let's start setting up our environment. For prerequisite, if you want to publish it on NPM. Make sure you have NPM and are logged in.

Initiate NPM

Since we want to use Nodejs and publish our application in NPM, it's good to initiate it with NPM.

npm init --scope=@alvinend

I want my application to be accessible via npx @alvinend/tic-tac-toe-cli. To do that, we can put scoped option --scope=@user-name and replace your "user-name with your username. More Info: docs.npmjs.com/cli/v8/using-npm/scope.

Install Package

Here is the list of the package we need:

npm i chalk figlet inquirer
  1. chalk Basically add color to console.log
  2. figlet Make BIG text in a terminal
  3. inquirer Enable us to ask a question in terminal

Building Tic Tac Toe

Home

Start simple, writing what this application is all about, a tic tac toe game. But it's boring to console log the title. I wanted to go flashy. Fortunately, with Figlet, I could do that!

We start by making a file named index.js.

// index.js

console.log(figlet.textSync('Tic Tac Toe'))

Output:

image.png

And maybe there is someone who does not know tic tac toe's rule. So I added it.

// index.js

console.log(`
===============================================================================================================
  ${chalk.yellow('How To Play')}

  ${chalk.yellow('1. Have the first player go first')}
  ${chalk.yellow('2. Have the second player go second.')} 
  ${chalk.yellow('3. Keep alternating moves until one of the players has drawn a row of three symbols or until no one can win.')}

  ${chalk.yellow('Any Question? Google it!')}
===============================================================================================================
`)

With chalk.js, we can color some text to make it less "terminal" like. Output:

image.png

And we are done. The first step is to be CLI Developer.

Player

First, we make a class that defines Player, setting order the variable which tells if the player is first or second to make a move and name to record the player's name.

// player.js

export class Player {
  constructor(order) {
    this.order = order
    this.name = `Player ${order}`
  }
}

We want the player to put their name in, so we add a function in that class to ask the player what their name is. Using inquirer, we can do that easily.

// player.js

async init() {
  const ans = await inquirer.prompt({
    name: 'name',
    type: 'input',
    message: 'What is your name?',
    default: `Player ${this.order}`
  })

  if (ans.name) {
    this.name = ans.name
  }
}

Finally, we want a function that lets players choose which spot they want to mark. Tic Tac Toe has nine spots, so we make an array of it.

// player.js

const MOVE_LIST = [
  '0 (Top Left)',
  '1 (Top Middle)',
  '2 (Top Right)',
  '3 (Middle Left)',
  '4 (Middle Middle)',
  '5 (Middle Right)',
  '6 (Bottom Left)',
  '7 (Bottom Middle)',
  '8 (Bottom Right)',
]

But we don't want players to choose spots with a mark on it. So we make a function that asks the player to select only empty spots.

// player.js

async calcMove(board) {
  const legalMoveList = MOVE_LIST.filter((_, index) => !board.table[index])

  const ans = await inquirer.prompt({
    name: 'move',
    type: 'list',
    message: `${this.name} Chose Your Move.`,
    choices: legalMoveList
  })


  return MOVE_LIST.findIndex(move => move === ans.move)
}

That is it for players!

Board

Board is a place where the player can put marks on it. As said before, Tic Tac Toe contains nine spots. We start by making a class that has board information in it. 0 are empty spots, 1 are spots with player order one's mark (O), and 2 are spots with player order two's mark (X).

// board.js

export class Board {
  constructor() {
    this.table = [0, 0, 0, 0, 0, 0, 0, 0, 0]
  }
}

If we want to make a board, we should print it like a board. Rather than printing the board's array, it makes sense to print a 2D board with marks on it.

// board.js

const MARKS = ['', 'O', 'X']

print() {
  const arr = []

  for (let i = 0; i < 3; i++) {
    let row = []

    for (let j = 0; j < 3; j++) {
      row.push(MARKS[this.table[i * 3 + j]])
    }

    arr.push(row.join(' | '))
  }

  console.log(arr.join('\n') + '\n')
}

Now we get into building the game rule. First, we want to enable the player to play it. And to do that, the player needs to put their marks on board. So we make the function of it.

// board.js

async place(player) {
  const pos = await player.calcMove(this)

  if (this.table[pos] == 0) {
    this.table[pos] = player.order
    return true
  }

  console.log('Placement Error, Already someone there')
  return false
}

A game is not a game if there is no winner. In Tic Tac Toe, you can win by putting three marks vertically, horizontally, or diagonally. There are many ways to do this. Here is my way. Feel free to make your version of it.

// board.js

checkIsWin(player) {
  const checkList = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8], 
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8], 
    [0, 4, 8],
    [2, 4, 6]
  ]

  for (let i = 0; i < checkList.length; i++) {
    const check = checkList[i]
    let count = 0

    for (let i = 0; i < check.length; i++) {
      if (this.table[check[i]] === player.order) {
        count = count + 1
      }

      if (count === 3) {
        return true
      }
    }
  }

  return false
}

And... we are done. We built a functional board class of Tic Tac Toe.

Match

We built Player and Board. The last thing we need is for those players to use Board and play with it.

  1. Declare board and player
  2. Loop to maximum move that Tic Tac Toe game can do (nine times)
  3. For each loop, the player places a mark on a spot, print the board, and check the winner
  4. Game end a player win or game reaches maximum move count
// match.js
import { Board } from "./board.js"
import { Player } from "./player.js"

export const startGame = async (board, firstPlayer, secondPlayer) => {
  // Init Board and Player
  board = new Board()

  firstPlayer = new Player(1)
  await firstPlayer.init()

  secondPlayer = new Player(2)
  await secondPlayer.init()

  // For recording turn
  let turn = 0

  for (let i = 0; i < 9; i++) {
    const inTurnPlayer = (turn % 2 + 1 === 1) ? firstPlayer : secondPlayer

    await board.place(inTurnPlayer)

    board.print()

    // Check If has winner
    if (board.checkIsWin(inTurnPlayer)) {
      console.log(`${inTurnPlayer.name} won!`)
      return
    }

    turn++
  }

  console.log('Draw!')
}

Call that function in index.js, and we are done!

// index.js

#!/usr/bin/env node

import chalk from "chalk";
import figlet from "figlet";
import { startGame } from "./match.js";

console.log(figlet.textSync('Tic Tac Toe'))

console.log(`
===============================================================================================================
  ${chalk.yellow('How To Play')}
  ${chalk.yellow('1. Have the first player go first')}
  ${chalk.yellow('2. Have the second player go second.')} 
  ${chalk.yellow('3. Keep alternating moves until one of the players has drawn a row of three symbols or until no one can win.')}
  ${chalk.yellow('Any Question? Google it!')}
===============================================================================================================
`)

startGame()

Publish to NPM

Publishing CLI is as easy as entering one line of command in the terminal.

npm publish

If this is your first time, you might run into errors, but almost everybody uses and publishes npm's packages, google, and you are set to go.

Ending

Congratulations, you published your own CLI application! In this article, I focused on building our own CLI and Tic Tac Toe. I hope it gives you a grasp of how npm CLI works.

If you are interested, here is my link to the complete code on this application

github.com/alvinend/tic-tac-toe-cli

Did you find this article valuable?

Support Alvin Endratno by becoming a sponsor. Any amount is appreciated!

ย