X_O

⭕Tic Tac Toe game built on mobile app using react native and redux

View the Project on GitHub mitulsavani/X_O

Tic Tac Toe - X_O

Welcome to Tic Tac Toe mobile application game, built with React Native.

Table of Contents

Overview

This is a Tic Tac Toe mobile application game where 2 players can play at the same time on a 3x3 grid. The app is built in react native framework and uses redux for the state management of the game.

Functionality

Demo

X_O

Victory Game Tied

I have publised the application on Expo but unfortunately, Expo does not allow clients to try the app publicly anymore. Therefore, I have provided information below to run the app locally.

Getting Started

Installation

git clone https://github.com/mitulsavani/X_O.git
yarn install
yarn run ios 
yarn run android

This project was bootstrapped with Expo CLI Quickstart .

Note: I could have initiated this project using React Native CLI for more flexibility on native side. However, I decided to use Expo CLI because I knew before hand that this application is a comparatively small demo application which would not require much third party libraries.

Game

Project Structure

X_O
|__ App.js
|__ package.json
|__ README.md
|__ .eslintrc.json
|__ .gitignore
|__ assests
|   |__ p1.png
|   |__ p2.png
|__ src
     |__ components
     |   |__ Board.js
     |   |__ Button.js
     |   |__ Cell.js
     |   |__ Player.js
     |__ navigation
     |   |__ RootNavigator.js
     |__ redux
     |   |__ actions.js
     |   |__ gameReducer.js
     |   |__ InitialState.js
     |   |__ root.reducer.js
     |   |__ store.js
     |__ screens
     |   |__ GameScreen.js
     |__ styles
         |__ colors.js
         |__ GameScreenStyles.js
         |__ components
             |__ BoardStyles.js
             |__ ButtonStyles.js
             |__ CellStyles.js
             |__ PlayerStyles.js

Custom components

The following components were built to complete the GameScreen:

All custom components have a set of props which I am validating using Prop-Types.

Screen & Navigation

The game have one screen called as GameScreen. I was intitally thinking to make multiple screens for this game, but I did not see a point in doing it because that would only increase player clicks to reach up to the main GameScreen and the navigation was acquired using react navigation.

Redux

State

export const InitialState = () => ({
  board: [
    [null, null, null],
    [null, null, null],
    [null, null, null]
  ],
  winner: null,
  currentPlayer: 'X',
  gameOver: false,
});

Actions

export const NEW_GAME = 'NEW_GAME';
export const UPDATE_CELL = 'UPDATE_CELL';
export const TOGGLE_PLAYER = 'TOGGLE_PLAYER';
export const CHECK_GAME_OVER = 'CHECK_GAME_OVER';

Reducer

The application has one reducer named as gameReducer.js which is responsible to return the updated state.

Algorithms Explanation

Update Board

From GameScreen.js :
I am fetching the rowIndex and colIndex of the cell when player clicks on the cell from the component props and then dispatching UPDATE_CELL action with the payload(rowIndex and colIndex).

From gameReducer.js :
I am directly updating the value of that cell on the board with the currentPlayer.

case UPDATE_CELL:
const { row, col } = action.payload;

state.board[row][col] = state.currentPlayer;

return {
...state,
}

Check Winner / Game Draw

From gameReducer.js :

There are in total of 8 ways a player can win this game on 3x3 board. let's look at those patterns:

const winningPatterns = [
  // Horizontals
  [{ r: 0, c: 0 }, { r: 0, c: 1 }, { r: 0, c: 2 }],
  [{ r: 1, c: 0 }, { r: 1, c: 1 }, { r: 1, c: 2 }],
  [{ r: 2, c: 0 }, { r: 2, c: 1 }, { r: 2, c: 2 }],

  // Verticals
  [{ r: 0, c: 0 }, { r: 1, c: 0 }, { r: 2, c: 0 }],
  [{ r: 0, c: 1 }, { r: 1, c: 1 }, { r: 2, c: 1 }],
  [{ r: 0, c: 2 }, { r: 1, c: 2 }, { r: 2, c: 2 }],

  // Diagonals
  [{ r: 0, c: 0 }, { r: 1, c: 1 }, { r: 2, c: 2 }],
  [{ r: 0, c: 2 }, { r: 1, c: 1 }, { r: 2, c: 0 }],
];

The function checks if the currentPlayer value matches with all the three positions of a single combination of the winningPatterns.

const checkWinner = (board) => {

  return winningPatterns.some(pattern => pattern.every(cell => {
    const { r, c } = cell;

    return board[r][c] === state.currentPlayer;
  }));
}

The function checks if our current board is full or not.

const isBoardFull = (board) => {

  const notFull = board.some(row => row.some(col => col === null));

  return !notFull;
}

The function checks whether a player has won or if the game is draw? By the end of the game, if all cell values are filled up and no player value have matched with one of the winningPatters then the game will be considered as draw.

Note: I am aware that the function returns three different kind of values (string, null, booleans). I believe that this is considered as bad practice, therefore I would definitely optimize this later.

const checkGameOver = (board) => {
  if(checkWinner(board)) {
    return state.currentPlayer;
  }
  if(isBoardFull(board)) {
    return null;
  }
  return false;
}

Toggle Player Turn

From gameReducer.js:

I am doing 2 things here; Firstly, checking if winner is already declared, if so then there is no point in altering player because game is over! Ortherwise, I would alter the player.

case TOGGLE_PLAYER:
if(state.winner !== null) 
  return state;

const nextPlayer = state.currentPlayer === 'X' ? 'O' : 'X';

return {
  ...state,
  currentPlayer: nextPlayer,
}

Alert

From GameScreen.js:

I am rendering alert based on winner and gameOver entity from the state. If gameReducer.js have found out a winner then pop Victory alert, otherwise render Game-Tied alert.

displayGameStatusAlert = () => {
  const { game } = this.props;
  const { winner, gameOver } = game;

  if(gameOver === true) {
    if(winner !== null) {
      return(
        Alert.alert(
          'Victory',
          `${winner} - Wins the game`,
          [{ text: 'Reset Game', onPress: () => this.resetGame()}],
          { cancelable: false }
        )
      )
    } else {
      return(
        Alert.alert(
          'Game Tied',
          'Play Again',
          [{ text: 'Reset Game', onPress: () => this.resetGame()}],
          { cancelable: false }
        )
      )
    }
  } 
}

When player presses the alert button, I am calling a resetGame() function which re-renders the game with the Initial_State.

resetGame() {
  this.props.newGame();
}

Future Ideas

handleClick(rowIndex, colIndex) {
  
  this.props.updateCell(rowIndex, colIndex);
  this.props.checkGameOver();
  this.props.togglePlayer();
}

Design Credits

Feedback

In case if you have any questions or feedback about this application, feel free to reach out to me @mitulsavani

created by Mitul Savani, updated on 01/14/2020