React JavaScript

Tic Tac Toe with History

This JavaScript program demonstrates how to program tic tac toe with a selectable history in React.

TicTacToeReact.html

<!DOCTYPE html>
	<html>
	<head>
		<title>XoaX.net's Javascript React</title>
		<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
		<script async src="https://ga.jspm.io/npm:es-module-shims@1.7.0/dist/es-module-shims.js"></script>
		<script type="importmap">
			{
				"imports": {
					"react": "https://esm.sh/react?dev",
					"react-dom/client": "https://esm.sh/react-dom/client?dev"
				}
			}
		</script>
		<script type="text/babel" data-type="module" src="TicTacToeReact.js"></script>
		<style>
* {
  box-sizing: border-box;
}

body {
  font-family: sans-serif;
  margin: 20px;
  padding: 0;
}

.cSquare {
	color: #444444;
  background: #EEEEEE;
  border: 1px solid #AAAAAA;
  float: left;
  font-size: 90px;
  font-weight: bold;
  line-height: 80px;
  height: 80px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 80px;
}

.cMoves li {
	list-style-type: square;
}

.cMoves li button {
	color: #222222;
	background-color: #EEEEEE;
	border: 1px solid #AAAAAA;
	border-radius: 5px;
	font-size: 16px;
	width: 180px;
	line-height: 22px;
	text-align: left;
}

.cBoardRow:after {
  clear: both;
  content: '';
  display: table;
}

#idGameState {
	font-size: 24px;
	font-weight: bold;
  margin-bottom: 10px;
}

.cGame {
  display: flex;
  flex-direction: row;
}

.cHistory {
  margin-left: 20px;
}
		</style>
	</head>
	<body>
		<div id="idRoot"></div>
	</body>
</html>

TicTacToeReact.js

// This requires a web service.
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

import { useState } from 'react';

function CheckForWinner(caSquareValues) {
	const iaaLines = [[0,1,2],[0,3,6],[0,4,8],[1,4,7],[2,4,6],[2,5,8],[3,4,5],[6,7,8]];
	for (let iLine = 0; iLine < iaaLines.length; ++iLine) {
		const [i, j, k] = iaaLines[iLine];
		if (caSquareValues[i] &&
			(caSquareValues[i] === caSquareValues[j]) &&
			(caSquareValues[i] === caSquareValues[k])) {
			return caSquareValues[i];
		}
	}
	// No winner yet and no draw condition
	for (let i = 0; i < 9; ++i) {
		if (caSquareValues[i] == null) {
			return null;
		}
	}
	return 'D'; // Draw condition 
}
function GetSquareElement({sCurrValue, fnClickHandler}) {
	return <button className="cSquare" onClick={fnClickHandler}>{sCurrValue}</button>
}
function GetBoard({bNextPlayerX, caSquareValues, fnPlayHandler}) {
	function fnHandleClick(iSquareIndex) {
		let bGameOver = (CheckForWinner(caSquareValues) != null);
		let bOccupied = (caSquareValues[iSquareIndex] != null);
		// If the game is over or the squared is occupied, return without changing the board.
		if (bGameOver || bOccupied) {
			return;
		} else {
			// Copy the array elements for the next turn.
			// This is a shallow copy, but it is okay because the elements are not objects.
			const caNextTurn = [...caSquareValues];
			if (bNextPlayerX) {
				caNextTurn[iSquareIndex] = 'X';
			} else {
				caNextTurn[iSquareIndex] = 'O';
			}
			fnPlayHandler(caNextTurn);
		}
	}
	
	const kcWinner = CheckForWinner(caSquareValues);
	let sGameStatus;
	if (kcWinner == 'O' || kcWinner == 'X') {
		sGameStatus = "The winner is " + kcWinner;
	} else if (kcWinner == null) {
		sGameStatus = "The next player is " + (bNextPlayerX ? 'X' : 'O');
	} else {
		sGameStatus = "The game is a draw";
	}

	return (<>
		<div id="idGameState">{sGameStatus}</div>
		<div className="cBoardRow">
			<GetSquareElement sCurrValue={caSquareValues[0]} fnClickHandler={() => fnHandleClick(0)} />
			<GetSquareElement sCurrValue={caSquareValues[1]} fnClickHandler={() => fnHandleClick(1)} />
			<GetSquareElement sCurrValue={caSquareValues[2]} fnClickHandler={() => fnHandleClick(2)} />
		</div>
		<div className="cBoardRow">
			<GetSquareElement sCurrValue={caSquareValues[3]} fnClickHandler={() => fnHandleClick(3)} />
			<GetSquareElement sCurrValue={caSquareValues[4]} fnClickHandler={() => fnHandleClick(4)} />
			<GetSquareElement sCurrValue={caSquareValues[5]} fnClickHandler={() => fnHandleClick(5)} />
		</div>
		<div className="cBoardRow">
			<GetSquareElement sCurrValue={caSquareValues[6]} fnClickHandler={() => fnHandleClick(6)} />
			<GetSquareElement sCurrValue={caSquareValues[7]} fnClickHandler={() => fnHandleClick(7)} />
			<GetSquareElement sCurrValue={caSquareValues[8]} fnClickHandler={() => fnHandleClick(8)} />
		</div>
	</>);
}


let App = function Game() {
  const [caaHistory, setHistory] = useState([Array(9).fill(null)]);
  const [iMoveIndex, setMoveIndex] = useState(0);
  const bNextPlayerIsX = (iMoveIndex % 2 === 0);
  const caCurrentBoard = caaHistory[iMoveIndex];

  function SetNextPlay(caNextSquares) {
    const qaNextHistory = [...caaHistory.slice(0, iMoveIndex + 1), caNextSquares];
    setHistory(qaNextHistory);
    setMoveIndex(qaNextHistory.length - 1);
  }

  function GoToMove(iMove) {
    setMoveIndex(iMove);
  }

  const qaMoveButtons = caaHistory.map((caCurrBoard, iCurrMove) => {
    let sButtonText;
    if (iCurrMove > 0) {
      sButtonText = 'Click for move #' + iCurrMove;
    } else {
      sButtonText = 'Click for the beginning';
    }
    return (
      <li key={iCurrMove}>
        <button onClick={() => GoToMove(iCurrMove)}>{sButtonText}</button>
      </li>
    );
  });

  return (
    <div className="cGame">
      <div>
        <GetBoard bNextPlayerX={bNextPlayerIsX} caSquareValues={caCurrentBoard} fnPlayHandler={SetNextPlay} />
      </div>
      <div className="cHistory">
        <ul className="cMoves">{qaMoveButtons}</ul>
      </div>
    </div>
  );
}

const root = createRoot(document.getElementById('idRoot'));
// App Specifies where to put the return value.
root.render(
	<StrictMode>
		<App />
	</StrictMode>
);
 

Output

 
 

© 2007–2026 XoaX.net LLC. All rights reserved.