Reputation: 3404
I'm following the tic tac toe tutorial.
I want to now add a feature where if there is a draw (nobody has won) then it updates the message with "There has been a draw". See the full snippet is below:
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}
render() {
return (
<div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null),
}],
xIsNext: true,
stepNumber: 0,
};
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{
squares: squares,
}]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext,
});
}
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0,
});
}
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
let status;
if (winner) {
status = 'Winner: ' + winner;
} elseif (winner === null) {
status = 'There has been a draw'
}
else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
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;
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
I'm trying to do this through inside the Game class. In the code that actually checks for the winner - if I'm understanding this right - it returns a value of null if none of the conditions are met. That relevant piece of code is this:
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;
}
What I want to do now, is in the code that references the above function, and displays a message for who has won, I'm setting an elseif to see if the function throws null, and if it does, show a message that a draw happened. That code looks like this:
let status;
if (winner) {
status = 'Winner: ' + winner;
} elseif (winner === null) {
status = 'There has been a draw'
}
else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
What is the purpose of return null;
in calculateWinner
function? How can I best check for a draw in the context of this application? Is there a better way to do it?
Codepen of problem: https://codepen.io/anon/pen/OOQxmw?editors=0010
Upvotes: 0
Views: 2868
Reputation: 66
I actually edited the status directly in the game component. If 10 steps have been made, then make it a draw.
let status;
if (winner) {
status = 'Winner: ' + winner;
} else if (this.state.stepNumber === 9) {
status = 'Draw! ';
} else {
status = 'Next Player: ' + (this.state.xIsNext ? 'X' : 'O');
}
Upvotes: 0
Reputation: 1197
In calculateWinner(), I changed:
return null;
to
for(let j = 0; j < squares.length; j++) {
if (squares[j] == null) {
return null;
}
}
return "DRAW";
And in render(), I changed:
if (winner) {
status = "Winner: " + winner;
else {
status = "Next player: " + (this.state.xIsNext ? "X" : "O");
}
to
if (winner) {
status = "Winner: " + winner;
//ADDED THIS NEW IF
if (status == "Winner: DRAW") {
status = "There has been a draw.";
}
//END OF NEW IF
}
else {
status = "Next player: " + (this.state.xIsNext ? "X" : "O");
}
Note: this isn't the best solution, just a way to circumvent complex codes.
Upvotes: 1
Reputation: 21
I was asking the same question. I'm learning Reactjs and soon React Native. I'm a junior programmer.
My solutions is a little bit different from @sherlockDev answer:
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const finalMove = 9; // We know that the game can have only 9 moves.
const moves = history.map(step, move) => {
const desc = move ?
'Back to round n°' + move :
'Back to the beginning';
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}>/button>
</li>
);
});
let status;
if (winner) {
status = 'Player ' + winner + ' win the game!';
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
if (this.state.stepNumber === finalMove){
status = 'Draw game!';//If we are in the last move and still no winner
}
}
Let me explain why I choose this code to define if there is a draw game or not.
First, I was thinking about creating something like the calculateWinner()
function, because I'm working on one who can give me the position of the winning games and convert it into a Chess form ( [A0, B4, C8] a diagonal winning game for example) but I was too stubborn and I did not see that it will not works outside the statement looking for a winner and most important I did not see that I've everything about the game, all winning games solutions, who start the game, etc...
Once I remember that the final move can only be const finalMove = 9
because of what we defined there:
`class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null), // Here we decide that the game will have 9 squares.
}];
// etc... you know the rest of it
`
If the game has 9 squares it can only have 9 moves.
The reason why I put it inside the statement looking for a winner is because of the } else {
.
This is what I understood after stopping to be stubborn:
If there have a winner: the game celebrates a winner; if there no winner: the game has 2 options: the game is not over or draw game.
So I decided to compare the actual move with the finalMove
variable who is 9. And as it's inside the } else {
it can only work when there have no winner yet.
The game is calculating the next player so I can add the new if
statement that looking for a match between those two variables.
Even if the last move is a winning game, the if (winner)
will works.
I hope this will help.
Best regards
Bk201
Upvotes: 1
Reputation: 11
If you edit your if
statement to
if(!winner && this.state.history.length === 10){
status = 'Draw';
}
you should get the result you are wanting.
Upvotes: 1
Reputation: 1347
I was looking around for the answer to this question as well as I am also new to react, I solved it by doing the following:
Change calculateWinner
to:
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];
}
else if(!squares.includes(null)){
return 'draw';
}
}
return null;
}
and change the winner logic in Game
class render
function to:
if(winner && winner != 'draw'){
status = 'Winner: ' + winner;
} else if (winner && winner === 'draw'){
status = "It's a " + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
Upvotes: 1
Reputation: 1271
calculateWinner()
is being called on every render, and returns null
if no winning lines are found on this render, which isn't necessarily the end of the game. You will need to check that no winners are found AND if the entire board is full. That would be the condition for a draw.
Upvotes: 1