From 064135ff19539f1a59148190cd4c80159146c667 Mon Sep 17 00:00:00 2001 From: Robert Spurlin Date: Mon, 23 Oct 2017 08:32:01 -0500 Subject: [PATCH] Create tic-tac-toe.js in React (#997) --- tic_tac_toe/javascript/react/tic-tac-toe.js | 348 ++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 tic_tac_toe/javascript/react/tic-tac-toe.js diff --git a/tic_tac_toe/javascript/react/tic-tac-toe.js b/tic_tac_toe/javascript/react/tic-tac-toe.js new file mode 100644 index 000000000..61934ad64 --- /dev/null +++ b/tic_tac_toe/javascript/react/tic-tac-toe.js @@ -0,0 +1,348 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; + + +// Initialization of variables. +let human, computer; + +// Dummy components. Only returns JSX to render when called by Board. +function Square(props) { + return ( + + ); +} + +function Button(props) { + return ( + + ); +} + + +// The actual board. Where all of the rendering of the actual game is, with the algorithim. + +class Board extends React.Component { + constructor(props) { + super(props); + this.state = { + squares: Array(9).fill(null), // Board definition. + xIsNext: props.isX, // Recieving from Game based on user input. Returns true or false + } + } + + +// Calls the square and adds clicking functions to it. + renderSquare(i) { + return ( + this.handleClick(i)} + /> + ); + } + + +// changes the state everytime a button is clicked, but tests if the game isn't over before + handleClick(i) { + + computer = this.state.xIsNext ? 'O' : 'X'; + human = this.state.xIsNext ? 'X' : 'O'; + + const squares = this.state.squares.slice(); + if (calculateWinner(squares) || squares[i] || isItATie(squares)) { + return; + } + squares[i] = this.state.xIsNext ? 'X' : 'O'; + this.setState({ + squares: squares, + xIsNext: !this.state.xIsNext, + }, + this.AIDecider + ); + } + + +// resets the board when the button is clicked. + resetClick() { + this.setState({ + squares: Array(9).fill(null), // Clears the board. + xIsNext: this.props.isX, // if true, returns 'X'. else, returns 'O'. + }); + } + + +// Easier to just define the function and keep calling it with this + copyBoard(board) { + return board.slice(); + } + + /* + Checks to make sure if the move that AIDecider, minScore, and maxScore is valid. + If not, doesn't return anything. + If it is valid, it returns every possible combination in arrays. + */ + + validMove(index, player, board) { + let thisCopy = this.copyBoard(board); + if (thisCopy[index] === null) { + thisCopy[index] = player; + return thisCopy; + } else { + return null; + } + } + + /* + The actual decider, and beginning of the MiniMax algorithim. + Calls maxScore, which calls minScore until it finds an index. + Then, copies the board and gives back the board with the decision. + */ + + AIDecider() { + let boardCopy = this.copyBoard(this.state.squares); + let move = null; + let bestMoveScore = -100; + let newBoard = null; + let publishedBoard = null; + // doesn't do anything if the game is over + if (calculateWinner(boardCopy) === computer || calculateWinner(boardCopy) === human || isItATie(boardCopy)) { + return null; + } + + /* + The initial loop that suggests any and all moves possible. Calls maxScore which + has its own loop which adds a move and calls validMove + (therefore putting it a move ahead), and maxScore calls minScore that has its own loop + (therefore putting it two moves ahead). Then, decides by point incentive whether + it is a good move or not. + */ + + for (let i = 0; i < boardCopy.length; i++) { + newBoard = this.validMove(i, computer, boardCopy); + if (newBoard) { + let moveScore = this.maxScore(newBoard); + if (moveScore > bestMoveScore) { + bestMoveScore = moveScore; + move = i; + } + } + } + + /* + When the loop is over, it will assign the index that is the best move to play. + Assigns the new index with whatever the computer is playing (X or O), then adds it onto + a copy of the board (publishedBoard) where it will be change the state of the board. + setTimeout given for user experience (giving the illusion that the computer is "thinking"). + */ + + publishedBoard = boardCopy; + publishedBoard[move] = computer; + setTimeout(() => { + this.setState({ + squares: publishedBoard, + xIsNext: !this.state.xIsNext + }); + }, 300); + } + + /* + minScore and maxScore call on each other where they add more possible moves onto + the copied board until it is either a tie, or someone has won with the theoretical + boards. Then, returns a number value where the loop in AIDecider will start again + until the loop runs out. + */ + + minScore(board) { + if (calculateWinner(board) === human) { + return -10; + } else if (calculateWinner(board) === computer) { + return 10; + } else if (isItATie(board)) { + return 0; + } else { + let bestMoveValue = -100; + let move = 0; + for (let i = 0; i < board.length; i++) { + let newBoard = this.validMove(i, computer, board); + if (newBoard) { + let predictedMoveValue = this.maxScore(newBoard); + if (predictedMoveValue > bestMoveValue) { + bestMoveValue = predictedMoveValue; + move = i; + } + } + } + return bestMoveValue; + } + } + + // Called first by AIDecider, then calls minScore + + maxScore(board) { + if (calculateWinner(board) === human) { + return -10; + } else if (calculateWinner(board) === computer) { + return 10; + } else if (isItATie(board)) { + return 0; + } else { + let bestMoveValue = 100; + let move = 0; + for (let i = 0; i < board.length; i++) { + let newBoard = this.validMove(i, human, board); + if (newBoard) { + let predictedMoveValue = this.minScore(newBoard); + if (predictedMoveValue < bestMoveValue) { + bestMoveValue = predictedMoveValue; + move = i; + } + } + } + return bestMoveValue; + } + } + + render() { + const winner = calculateWinner(this.state.squares); + const tie = isItATie(this.state.squares); + let status, button = null; + + if (winner) { + status = "Winner: " + winner; + button = + + + ); + } + + test(bool) { + if (bool) { + this.setState({ + hasChosen: true, + isX: true, + }); + } else { + this.setState({ + hasChosen: true, + isX: false, + }); + } + } + + render() { + let board = null; + + if (!this.state.hasChosen) { + board = this.question(); + } else { + board = + } + + return ( +
+
+
+ {board} +
+
+
+ ); + } +} + +/* +Where it calculates if there is a possible winner with the MiniMax loops, or ties. +Note: status does not render until there is an actual winner (not theoretical winners +made up by MiniMax), tested by the actual state of the board. +*/ + +function calculateWinner(squares) { + const lines = [ + [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 < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +} + +function isItATie(squares) { + let copy = squares.slice(); + if (copy.some(element => !element)) { + return false; + } else { + return true; + } +} + +ReactDOM.render( + , + document.getElementById('root') +);