This JavaScript program demonstrates how to program tic tac toe with a selectable history in React.
<!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>// 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> );
© 20072026 XoaX.net LLC. All rights reserved.