Recursion issue with tic-tac-toe minimax - javascript

I'm trying to use minimax but it's getting stuck, can anyone shed some light? The method is computerBestMove above the body of HTML stuff.
When I put in a bugger it leads to stack too deep.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#game_board {
border-style: solid
}
th {
border-style: inset;
font-size: 20px;
text-align: center;
width: 50px;
height: 50px;
}
</style>
<script>
var gameOver = false;
var state = [0,0,0,0,0,0,0,0,0];
var human = false
var computer = true
var humanValue = -1;
var compValue = 1;
var winCombo = [
[0,1,2],
[3,4,5],
[6,7,8],
[0,3,6],
[1,4,7],
[2,5,8],
[0,4,8],
[2,4,6]
]
var squares = document.getElementsByClassName("square");
function markSquare(square) {
if(gameOver === false ) {
for(var x = 0; x < 9; x++ ) {
if(squares[x] == square && state[x] == 0) {
set(x, humanValue);
computerMove();
}
}
} else {
console.log("game is over");
}
}
function set(index, player) {
if(player == humanValue) {
squares[index].innerText = humanValue;
state[index] = humanValue;
} else {
squares[index].innerText = compValue;
state[index] = compValue;
}
}
function checkWinner(board, player) {
var value = player === human ? humanValue : compValue;
//8 differnt way of winning
winCombo.forEach(function(combo) {
if (value === state[combo[0]] &&
state[combo[0]] === state[combo[1]] &&
state[combo[1]] === state[combo[2]]) {
gameOver = true;
console.log(value + " wins!");
}
})
return gameOver;
// for(var x = 0; x < 8; x++) {
// var win = true;
// for(var y = 0; y < 3; y++){
// if(board[winCombo[x][y]] != value) {
// win = false;
// break;
// }
// }
// if(win) {
// return true;
// }
// }
// return false;
}
function returnWinner() {
var winner = null;
winCombo.forEach(function(combo) {
if (state[combo[0]] === state[combo[1]] && state[combo[1]] === state[combo[2]]) {
(combo[0] === -1 ) ? (winner = "player") : (winner = "computer");
}
})
return winner;
}
function noMoreMove(board) {
for (var i = 0; i < 9; i++) {
if (board[i] === 0) {
return false;
} else
return true;
}
}
function computerMove() {
computerBestMove(state, 0, computer)
}
function computerBestMove(board, depth, player) {
//if player wins ... = -10
if(checkWinner(board, !player) === true) {
return -10 + depth;
}else if(noMoreMove(board) === true) {
return 0;
}
var value = player === human ? humanValue : compValue;
var max = -Infinity;
var index = 0;
for(var i = 0; i < 9; i++){
if(board[i] === 0) {
var newBoard = board.slice();
newBoard[i] = value;
var moveValue = -computerBestMove(newBoard, depth+1, !player);
if(moveValue > max) {
max = moveValue;
index = i;
}
}
}
if(depth === 0){
set(index, computer)
}
return max
}
</script>
</head>
<body>
<p id="message">A the message area.</p>
<button type="button" name = "choice" value = "two players" checked>Two players </button>
<button type="button" name = "choice" value = "vs AI">Unbeatable AI </button>
<table id='game_board'>
<tr>
<th id="square0" class = 'square' onclick="markSquare(this);"></th>
<th id="square1" class = 'square' onclick="markSquare(this);"></th>
<th id="square2" class = 'square' onclick="markSquare(this);"></th>
</tr>
<tr>
<th id="square3" class = 'square' onclick="markSquare(this);"></th>
<th id="square4" class = 'square' onclick="markSquare(this);"></th>
<th id="square5" class = 'square' onclick="markSquare(this);"></th>
</tr>
<tr>
<th id="square6" class = 'square' onclick="markSquare(this);"></th>
<th id="square7" class = 'square' onclick="markSquare(this);"></th>
<th id="square8" class = 'square' onclick="markSquare(this);"></th>
</tr>
</table>
</body>
</html>

This piece of code is not correct. If you return from a function, the loop automatically stops. So this will only check the first cell each time.
Once you replace it with a proper loop that checks all cells, then you get a stack overflow as you say.
function noMoreMove(board) {
for (var i = 0; i < 9; i++) {
if (board[i] === 0) {
return false;
} else
return true;
}
}
The stack overflow issue is caused by this line of code:
var moveValue = -computerBestMove(newBoard, depth+1, !player);
This is just incorrect logic. The only time computerBestMove() doesn't create a infinite loop, is when either a player has won, which is impossible the first 3 moves. Or when there are no more empty cells, which after fixing the noMoreMove function also never happens the first 8 moves. Both are checked at the start.
So you're always triggering this infinite loop.
You have to rewrite the computerBestMove() function so that the parts that check which move is best and the part that gives the result are split. You want to loop checking for the best position, but that loop has to stop once all combos have been checked.

Related

Hide faces of a box geometry that won't be seen by the camera in THREE.js

I'm making a project that includes many squares and if too many are rendered than it makes it really laggy. Is there a way to check if there is a block right next to it and not render a face of it in order to make it run faster?
Currently I am making boxes using 6 different planes and moving them around to make a box. I am aware this is not the most efficient way to do it and I have fixed that as of right now and that was the best way for my little brain to visualize it. Here is the code that checks if there is another box next to it:
let blRight = false
let blLeft = false
let blBack = false
let blFront = false
let blTop = false
let blBottom = false
for(let i = 0; i < blocks.coord.x.length; i++) {
if(blocks.coord.x[i] == x + 10) {
if(!blocks.coord.y[i] == y) {
blRight = true
}
}
}
for(let i = 0; i < blocks.coord.x.length; i++) {
if(blocks.coord.x[i] == x - 10) {
if(!blocks.coord.y[i] == y) {
blLeft = true
}
}
}
for(let i = 0; i < blocks.coord.y.length; i++) {
if(blocks.coord.y[i] == y + 10) {
blTop = false
}
}
for(let i = 0; i < blocks.coord.y.length; i++) {
if(blocks.coord.y[i] == y - 10) {
blBottom = true
}
}
for(let i = 0; i < blocks.coord.z.length; i++) {
if(blocks.coord.z[i] == z + 10) {
if(!blocks.coord.y[i] == y) {
blFront = true
}
}
}
for(let i = 0; i < blocks.coord.z.length; i++) {
if(blocks.coord.z[i] == x - 10) {
if(!blocks.coord.y[i] == y) {
blBack = true
}
}
}
if(blRight == false) {
scene.add(right)
}
if(blLeft == false) {
scene.add(left)
}
if(blTop == false) {
scene.add(blockTop)
}
if(blBottom == false) {
scene.add(bottom)
}
if(blFront == false) {
scene.add(front)
}
if(blBack == false) {
scene.add(back)
}
blocks.coord.x.push(x)
blocks.coord.y.push(y)
blocks.coord.z.push(z)
blocks.coord.id.push(3)
This is in a function that is called to make a new block. blTop meaning top face and so on. This is actually how Minecraft makes its game faster. Any suggestions to make it more effecient?

I made a Tic-tac-toe game with JavaScript, but I don’t know the add code

All basic program code is implemented and works well.
However, I would like to include a function that clears all cell values and restarts them when all nine cell compartments are filled.
I put my code on the tin, and it’s not working.
I’d like you to give me some advice on adding this function.
let table = document.querySelector('table');
let cell = [];
let line = [];
let text = document.querySelector('p');
let turn = 'X';
let chance = [];
for (i = 0; i < 3; i++) {
const lineEach = document.createElement('tr');
cell.push([]);
line.push(lineEach);
table.appendChild(lineEach);
for (j = 0; j < 3; j++) {
const cellEach = document.createElement('td');
cell[i].push(cellEach);
lineEach.appendChild(cellEach);
cellEach.addEventListener('click', start);
}
}
function start(event) {
let numLineEach = line.indexOf(event.target.parentNode);
let numCellEachEach = cell[numLineEach].indexOf(event.target);
if (cell[numLineEach][numCellEachEach].textContent === '') {
cell[numLineEach][numCellEachEach].textContent = turn;
let result = false;
if (cell[numLineEach][0].textContent === turn && cell[numLineEach][1].textContent === turn && cell[numLineEach][2].textContent === turn) {
result = true;
}
if (cell[0][numCellEachEach].textContent === turn && cell[1][numCellEachEach].textContent === turn && cell[2][numCellEachEach].textContent === turn) {
result = true;
}
if (numLineEach - numCellEachEach === 0) {
if (cell[0][0].textContent === turn && cell[1][1].textContent === turn && cell[2][2].textContent === turn) {
result = true;
}
}
if (Math.abs(numLineEach - numCellEachEach) === 2 || Math.abs(numLineEach - numCellEachEach) === 0) {
if (cell[0][2].textContent === turn && cell[1][1].textContent === turn && cell[2][0].textContent === turn) {
result = true;
}
}
if (result) {
text.innerText = turn + " turn win!";
cell.forEach(function(lineEach) {
lineEach.forEach(function(cellEach) {
cellEach.innerText = '';
})
})
} else {
if (turn === 'X') {
turn = "O";
} else {
turn = 'X';
}
}
} else {
text.innerText = 'no!';
}
}
/*
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
chance.push(cell[i][j].textContent);
var number = chance.indexOf('');
if (number === -1) {
text.innerText = " no winner";
cell.forEach(function(lineEach) {
lineEach.forEach(function(cellEach) {
cellEach.innerText = '';
})
})
}
}
}
*/
table, tr, td {
border: 1px solid black;
border-collapse: collapse;
text-align: center;
font-size: 2rem;
font-weight: bolder;
}
td {
width: 150px;
height: 150px
}
<table></table>
<p></p>
Note that I can’t speak English, so I’m asking this question using a translator.
you don't really need a new function. Just modify your start function a little like this:
although, if you wanted to create a new game button that would restart the game a new function would make sense. Let me know if that's what you want to do
let table = document.querySelector('table');
let cell = [];
let line = [];
let text = document.querySelector('p');
let turn = 'X';
let chance = [];
var nineCnt = 0;
for (i = 0; i < 3; i++) {
const lineEach = document.createElement('tr');
cell.push([]);
line.push(lineEach);
table.appendChild(lineEach);
for (j = 0; j < 3; j++) {
const cellEach = document.createElement('td');
cell[i].push(cellEach);
lineEach.appendChild(cellEach);
cellEach.addEventListener('click', start);
}
}
function start(event) {
let numLineEach = line.indexOf(event.target.parentNode);
let numCellEachEach = cell[numLineEach].indexOf(event.target);
if (cell[numLineEach][numCellEachEach].textContent === '') {
cell[numLineEach][numCellEachEach].textContent = turn;
nineCnt++;
if(nineCnt == 9){
var tds = document.getElementsByTagName('td')
for(let i = 0; i< 9; i++){
tds[i].innerHTML = '';
}
}
let result = false;
if (cell[numLineEach][0].textContent === turn && cell[numLineEach][1].textContent === turn && cell[numLineEach][2].textContent === turn) {
result = true;
}
if (cell[0][numCellEachEach].textContent === turn && cell[1][numCellEachEach].textContent === turn && cell[2][numCellEachEach].textContent === turn) {
result = true;
}
if (numLineEach - numCellEachEach === 0) {
if (cell[0][0].textContent === turn && cell[1][1].textContent === turn && cell[2][2].textContent === turn) {
result = true;
}
}
if (Math.abs(numLineEach - numCellEachEach) === 2 || Math.abs(numLineEach - numCellEachEach) === 0) {
if (cell[0][2].textContent === turn && cell[1][1].textContent === turn && cell[2][0].textContent === turn) {
result = true;
}
}
if (result) {
text.innerText = turn + " turn win!";
cell.forEach(function (lineEach) {
lineEach.forEach(function (cellEach) {
cellEach.innerText = '';
})
})
}
else {
if (turn === 'X') {
turn = "O";
}
else {
turn = 'X';
}
}
}
else {
text.innerText = 'no!';
}
}
/*
for(var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
chance.push(cell[i][j].textContent);
var number = chance.indexOf('');
if(number === -1){
text.innerText = " no winner";
cell.forEach(function (lineEach) {
lineEach.forEach(function (cellEach) {
cellEach.innerText = '';
})
})
}
}
}
*/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table,
tr,
td {
border: 1px solid black;
border-collapse: collapse;
text-align: center;
font-size: 2rem;
font-weight: bolder;
}
td {
width: 150px;
height: 150px
}
</style>
</head>
<body>
<table></table>
<p></p>
<script src='tictactoe.js'></script>
</body>
</html>

Solving TicTacToe with minimax algorithm in Javascript

let _board = [[null, null, null], [null, null, null], [null, null, null]];
let _flag = true;
let _AIrowIndex = null;
let _AIcellIndex = null;
const _wrapper = document.querySelector(".wrapper");
const _changeTurn = function () {
if (_flag == true) {
_flag = false;
return playerOne.getSign();
} else {
_flag = true;
return playerTwo.getSign();
}
};
const _displayTurn = function () {
let turn = document.querySelector(".playerInfo__turn")
if (_flag == true) {
turn.innerHTML = `${playerOne.getName()} is your turn`;
} else {
turn.innerHTML = `${playerTwo.getName()} is your turn`;
}
};
const _evaluation = (winner) => {
if(winner == "X"){
return 1;
}else if(winner == "O"){
return -1;
}
else{
return null;
}
};
const _evaluationFunction = function (board) {
/*CHECK 1 DIAG*/
if (board[0][0] === board[1][1] && board[2][2] === board[0][0]) {
return _evaluation(board[0][0]);
/*CHECK 2 DIAG*/
}
if (board[0][2] === board[1][1] && board[2][0] === board[0][2]) {
return _evaluation(board[0][2]);
/*CHECK PAIR*/
}
for (let col = 0; col < 3; col++) {
if (board[0][col] === board[1][col] && board[1][col] === board[2][col]) {
return _evaluation(board[0][col]);
}
}
for (let row = 0; row < 3; row++) {
if (board[row][0] === board[row][1] && board[row][1] === board[row][2]) {
return _evaluation(board[row][0]);
}
}
return 0;
};
const minimax = (_board, depth, isMaximizer) => {
let result = _evaluationFunction(_board);
console.log(result);
if (result !== null) {
return result;
}
if (isMaximizer) {
let bestScore = -Infinity;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (_board[i][j] == null) {
_board[i][j] = playerOne.getSign();
let score = minimax(_board, depth + 1, false);
_board[i][j] = null;
bestScore = Math.max(score, bestScore);
}
}
}
return bestScore;
} else {
let bestScore = Infinity;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (_board[i][j] == null) {
_board[i][j] = playerTwo.getSign();
let score = minimax(_board, depth + 1, true);
_board[i][j] = null;
bestScore = Math.min(score, bestScore);
}
}
}
return bestScore;
}
};
const _setAIPlay = () => {
let bestScore = Infinity;
let bestMove;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (_board[i][j] == null) {
_board[i][j] = playerTwo.getSign();
let score = minimax(_board, 0, true);
_board[i][j] = null;
if(score < bestScore){
bestScore = score;
console.log(bestScore);
bestMove = {i, j}
}
}
}
};
_board[bestMove.i][bestMove.j] = playerTwo.getSign();
_AIrowIndex = bestMove.i;
_AIcellIndex = bestMove.j;
_displayAIPlay(_AIrowIndex, _AIcellIndex);
_changeTurn();
_checkWinner();
};
const _displayAIPlay = (rowIndex, cellIndex) => {
let AIcell = document.querySelector(`[data-row="${rowIndex}"][data-cell="${cellIndex}"]`);
AIcell.textContent = playerTwo.getSign();
}
I am trying to solve this tic-tac-toe problem with the minimax algorithm but I don't understand why it continues to place the "O" in the adjacent cell, I tried to console.log() result, and best score inside the minimax function and it looks like the recursion is working, but I don't understand why inside _setAIPlay()
if I console.log(bestScore) in the last if statement it returns me as final value or 0 or 1 and not -1 which in this case I think should be the bestscore as a minimizer.
If needed here you can find the full repo gitHub
Here is a simple implementation of a minimax algorithm for Tic Tac Toe in JavaScript. The search tree deepens until a game-over state is detected. If a win is detected, then the current player cannot play, and so the static value is negative (-10). If a draw is detected the returned value is 0.
In all other cases the search deepens. This implementation uses a minimax algorithm where the value is maximised for both players. Therefore the sign of the value coming back from a deeper evaluation is always flipped ("what is good for my opponent is bad for me, and vice versa").
If a board is found to have a winning position then the moves that lead to the shortest path to a win are prioritised. This is achieved by lowering the absolute value by 1 point every time we backtrack.
If there are several moves with an equal value, then a random one will be picked from those.
Here is the implementation, with some basic HTML:
class TicTacToe {
constructor() {
this.board = Array(9).fill(0); // 0 means "empty"
this.moves = [];
this.isWin = this.isDraw = false;
}
get turn() { // returns 1 or 2
return 1 + this.moves.length % 2;
}
get validMoves() {
return [...this.board.keys()].filter(i => !this.board[i])
}
play(move) { // move is an index in this.board
if (this.board[move] !== 0 || this.isWin) return false; // invalid move
this.board[move] = this.turn; // 1 or 2
this.moves.push(move);
// Use regular expression to detect any 3-in-a-row
this.isWin = /^(?:...)*([12])\1\1|^.?.?([12])..\2..\2|^([12])...\3...\3|^..([12]).\4.\4/.test(this.board.join(""));
this.isDraw = !this.isWin && this.moves.length === this.board.length;
return true;
}
takeBack() {
if (this.moves.length === 0) return false; // cannot undo
this.board[this.moves.pop()] = 0;
this.isWin = this.isDraw = false;
return true;
}
minimax() {
if (this.isWin) return { value: -10 };
if (this.isDraw) return { value: 0 };
let best = { value: -Infinity };
for (let move of this.validMoves) {
this.play(move);
let {value} = this.minimax();
this.takeBack();
// Reduce magnitude of value (so shorter paths to wins are prioritised) and negate it
value = value ? (Math.abs(value) - 1) * Math.sign(-value) : 0;
if (value >= best.value) {
if (value > best.value) best = { value, moves: [] };
best.moves.push(move); // keep track of equally valued moves
}
}
return best;
}
goodMove() {
let {moves} = this.minimax();
// Pick a random move when there are choices:
return moves[Math.floor(Math.random() * moves.length)];
}
}
(function main() {
const table = document.querySelector("#game");
const btnNewGame = document.querySelector("#newgame");
const btnCpuMove = document.querySelector("#cpumove");
const messageArea = document.querySelector("#message");
let game, human;
function display() {
game.board.forEach((cell, i) => table.rows[Math.floor(i / 3)].cells[i % 3].className = " XO"[cell]);
messageArea.textContent = game.isWin ? (game.turn == human ? "CPU won" : "You won")
: game.isDraw ? "It's a draw"
: game.turn == human ? "Your turn"
: "CPU is preparing move...";
table.className = game.isWin || game.isDraw || game.turn !== human ? "inactive" : "";
}
function computerMove() {
if (game.isWin || game.isDraw) return;
human = 3 - game.turn;
display();
setTimeout(() => {
game.play(game.goodMove());
display();
}, 500); // Artificial delay before computer move is calculated and played
}
function humanMove(i) {
if (game.turn !== human || !game.play(i)) return; // ignore click when not human turn, or when invalid move
display();
computerMove();
}
function newGame() {
game = new TicTacToe();
human = 1;
display();
}
table.addEventListener("click", e => humanMove(e.target.cellIndex + 3 * e.target.parentNode.rowIndex));
btnNewGame.addEventListener("click", newGame);
btnCpuMove.addEventListener("click", computerMove);
newGame();
})();
#game { border-collapse: collapse }
#game td { border: 1px solid black; width: 30px; height: 30px; text-align: center; cursor: pointer }
#game td.X, #game td.O { cursor: default }
#game td.X { color: green }
#game td.O { color: red }
#game td.X:after { content: "X" }
#game td.O:after { content: "O" }
#game.inactive { background: silver }
<table id="game">
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
</table>
<h4 id="message"></h4>
<button id="newgame">New Game</button>
<button id="cpumove">Let CPU play a move</button>
class TicTacToe {
constructor() {
this.board = Array(9).fill(0); // 0 means "empty"
this.moves = [];
this.isWin = this.isDraw = false;
}
get turn() { // returns 1 or 2
return 1 + this.moves.length % 2;
}
get validMoves() {
return [...this.board.keys()].filter(i => !this.board[i])
}
play(move) { // move is an index in this.board
if (this.board[move] !== 0 || this.isWin) return false; // invalid move
this.board[move] = this.turn; // 1 or 2
this.moves.push(move);
// Use regular expression to detect any 3-in-a-row
this.isWin = /^(?:...)*([12])\1\1|^.?.?([12])..\2..\2|^([12])...\3...\3|^..([12]).\4.\4/.test(this.board.join(""));
this.isDraw = !this.isWin && this.moves.length === this.board.length;
return true;
}
takeBack() {
if (this.moves.length === 0) return false; // cannot undo
this.board[this.moves.pop()] = 0;
this.isWin = this.isDraw = false;
return true;
}
minimax() {
if (this.isWin) return { value: -10 };
if (this.isDraw) return { value: 0 };
let best = { value: -Infinity };
for (let move of this.validMoves) {
this.play(move);
let {value} = this.minimax();
this.takeBack();
// Reduce magnitude of value (so shorter paths to wins are prioritised) and negate it
value = value ? (Math.abs(value) - 1) * Math.sign(-value) : 0;
if (value >= best.value) {
if (value > best.value) best = { value, moves: [] };
best.moves.push(move); // keep track of equally valued moves
}
}
return best;
}
goodMove() {
let {moves} = this.minimax();
// Pick a random move when there are choices:
return moves[Math.floor(Math.random() * moves.length)];
}
}
(function main() {
const table = document.querySelector("#game");
const btnNewGame = document.querySelector("#newgame");
const btnCpuMove = document.querySelector("#cpumove");
const messageArea = document.querySelector("#message");
let game, human;
function display() {
game.board.forEach((cell, i) => table.rows[Math.floor(i / 3)].cells[i % 3].className = " XO"[cell]);
messageArea.textContent = game.isWin ? (game.turn == human ? "CPU won" : "You won")
: game.isDraw ? "It's a draw"
: game.turn == human ? "Your turn"
: "CPU is preparing move...";
table.className = game.isWin || game.isDraw || game.turn !== human ? "inactive" : "";
}
function computerMove() {
if (game.isWin || game.isDraw) return;
human = 3 - game.turn();
display();
setTimeout(() => {
game.play(game.goodMove());
display();
}, 500); // Artificial delay before computer move is calculated and played
}
function humanMove(i) {
if (game.turn !== human || !game.play(i)) return; // ignore click when not human turn, or when invalid move
display();
computerMove();
}
function newGame() {
game = new TicTacToe();
human = 1;
display();
}
table.addEventListener("click", e => humanMove(e.target.cellIndex + 3 * e.target.parentNode.rowIndex));
btnNewGame.addEventListener("click", newGame);
btnCpuMove.addEventListener("click", computerMove);
newGame();
})();
#game { border-collapse: collapse }
#game td { border: 1px solid black; width: 30px; height: 30px; text-align: center; cursor: pointer }
#game td.X, #game td.O { cursor: default }
#game td.X { color: green }
#game td.O { color: red }
#game td.X:after { content: "X" }
#game td.O:after { content: "O" }
#game.inactive { background: silver }
<table id="game">
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
</table>
<h4 id="message"></h4>
<button id="newgame">New Game</button>
<button id="cpumove">Let CPU play a move</button>
class TicTacToe {
constructor() {
this.board = Array(9).fill(0); // 0 means "empty"
this.moves = [];
this.isWin = this.isDraw = false;
}
get turn() { // returns 1 or 2
return 1 + this.moves.length % 2;
}
get validMoves() {
return [...this.board.keys()].filter(i => !this.board[i])
}
play(move) { // move is an index in this.board
if (this.board[move] !== 0 || this.isWin) return false; // invalid move
this.board[move] = this.turn; // 1 or 2
this.moves.push(move);
// Use regular expression to detect any 3-in-a-row
this.isWin = /^(?:...)*([12])\1\1|^.?.?([12])..\2..\2|^([12])...\3...\3|^..([12]).\4.\4/.test(this.board.join(""));
this.isDraw = !this.isWin && this.moves.length === this.board.length;
return true;
}
takeBack() {
if (this.moves.length === 0) return false; // cannot undo
this.board[this.moves.pop()] = 0;
this.isWin = this.isDraw = false;
return true;
}
minimax() {
if (this.isWin) return { value: -10 };
if (this.isDraw) return { value: 0 };
let best = { value: -Infinity };
for (let move of this.validMoves) {
this.play(move);
let {value} = this.minimax();
this.takeBack();
// Reduce magnitude of value (so shorter paths to wins are prioritised) and negate it
value = value ? (Math.abs(value) - 1) * Math.sign(-value) : 0;
if (value >= best.value) {
if (value > best.value) best = { value, moves: [] };
best.moves.push(move); // keep track of equally valued moves
}
}
return best;
}
goodMove() {
let {moves} = this.minimax();
// Pick a random move when there are choices:
return moves[Math.floor(Math.random() * moves.length)];
}
}
(function main() {
const table = document.querySelector("#game");
const btnNewGame = document.querySelector("#newgame");
const btnCpuMove = document.querySelector("#cpumove");
const messageArea = document.querySelector("#message");
let game, human;
function display() {
game.board.forEach((cell, i) => table.rows[Math.floor(i / 3)].cells[i % 3].className = " XO"[cell]);
messageArea.textContent = game.isWin ? (game.turn == human ? "CPU won" : "You won")
: game.isDraw ? "It's a draw"
: game.turn == human ? "Your turn"
: "CPU is preparing move...";
table.className = game.isWin || game.isDraw || game.turn !== human ? "inactive" : "";
}
function computerMove() {
if (game.isWin || game.isDraw) return;
human = 3 - game.turn;
display();
setTimeout(() => {
game.play(game.goodMove());
display();
}, 500); // Artificial delay before computer move is calculated and played
}
function humanMove(i) {
if (game.turn !== human || !game.play(i)) return; // ignore click when not human turn, or when invalid move
display();
computerMove();
}
function newGame() {
game = new TicTacToe();
human = 1;
display();
}
table.addEventListener("click", e => humanMove(e.target.cellIndex + 3 * e.target.parentNode.rowIndex));
btnNewGame.addEventListener("click", newGame);
btnCpuMove.addEventListener("click", computerMove);
newGame();
})();
#game { border-collapse: collapse }
#game td { border: 1px solid black; width: 30px; height: 30px; text-align: center; cursor: pointer }
#game td.X, #game td.O { cursor: default }
#game td.X { color: green }
#game td.O { color: red }
#game td.X:after { content: "X" }
#game td.O:after { content: "O" }
#game.inactive { background: silver }
<table id="game">
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
</table>
<h4 id="message"></h4>
<button id="newgame">New Game</button>
<button id="cpumove">Let CPU play a move</button>

Display image before reloading the page

I am trying to create a tic tac toe game using jquery.
This is my code :
var i, j, m, k;
$(document).ready(function() {
i = 0;
m = new Array(3);
for (var l = 0; l < 3; l++) {
m[l] = new Array(3);
for (k = 0; k < 3; k++)
m[l][k] = null;
}
});
$(".game").click(function() {
var img;
var col = $(this).index();
var row = $(this).closest('tr').index();
if (i % 2 == 0) {
img = '<img src="file:///c:/NNK/TicTacToe/TicTacToeV1/WebContent/WEB-INF/tictactoeO.jpe"/>';
m[row][col] = 'O';
} else {
img = '<img src="file:///c:/NNK/TicTacToe/TicTacToeV1/WebContent/WEB-INF/tictactoeX.jpe"/>';
m[row][col] = 'X';
}
$(this).prepend(img);
$(this).off();
i++;
var col = $(this).index();
var row = $(this).closest('tr').index();
if (i <= 8) {
if (winhor(row, col)) {
alert(m[row][col] + " Won!");
location.reload();
}
if (winver(row, col)) {
alert(m[row][col] + " Won!");
location.reload();
}
if (windiag(row, col)) {
alert(m[row][col] + " Won!Diag");
location.reload();
}
} else {
alert("Draw");
location.reload();
}
});
function winhor(row, col) {
var sym = m[row][col];
var val;
var check = true;
for (val = 0; val < 3; val++) {
if (sym != m[row][val]) {
check = false;
break;
}
}
return check;
}
function winver(row, col) {
var sym = m[row][col];
var val;
var check = true;
for (val = 0; val < 3; val++) {
if (sym != m[val][col]) {
check = false;
break;
}
}
return check;
}
function windiag(row, col) {
var sym = m[row][col];
var valr, valc;
var check = true;
if ((row != col) && (row + col != 2)) {
//alert("not 0 or 3 or 2");
return false;
} else if (row == col) {
for (valr = 0, valc = 0; valr < 3; valr++) {
if (sym != m[valr][valc]) {
//alert("not equal at "+valr+" "+valc);
check = false;
break;
}
valc++;
}
}
if (row + col == 2) {
check = true;
for (valr = 0, valc = 2; valr < 3; valr++) {
if (sym != m[valr][valc]) {
//alert("not equal at "+valr+" "+valc);
check = false;
break;
}
valc--;
}
}
return check;
}
td {
height: 100px;
width: 50px;
font-size=30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<table border="1">
<tr>
<td class="game"></td>
<td class="game"></td>
<td class="game"></td>
</tr>
<tr>
<td class="game"></td>
<td class="game"></td>
<td class="game"></td>
</tr>
<tr>
<td class="game"></td>
<td class="game"></td>
<td class="game"></td>
</tr>
</table>
When a player wins the alert message is displayed first before the image is put in the cell. I want the image to appear before the alert message is shown. how should i do it?
You have just to wait for a while then show the alert() box, you could use setTimeout() by 100ms for example, check the example bellow :
setTimeout(function(){
if (winhor(row, col)) {
alert(m[row][col] + " Won!");
location.reload();
}
if (winver(row, col)) {
alert(m[row][col] + " Won!");
location.reload();
}
if (windiag(row, col)) {
alert(m[row][col] + " Won!Diag");
location.reload();
}
},100)
Hope this helps.
var i, j, m, k;
$(document).ready(function() {
i = 0;
m = new Array(3);
for (var l = 0; l < 3; l++) {
m[l] = new Array(3);
for (k = 0; k < 3; k++)
m[l][k] = null;
}
});
$(".game").click(function() {
var img;
var col = $(this).index();
var row = $(this).closest('tr').index();
if (i % 2 == 0) {
img = '<img src="http://hhscp.org/programming/static/exemplars/tictactoe/X.png"/>';
m[row][col] = 'O';
} else {
img = '<img src="http://www.dreamincode.net/forums/uploads/post-97990-1260678636.png"/>';
m[row][col] = 'X';
}
$(this).prepend(img);
$(this).off();
i++;
var col = $(this).index();
var row = $(this).closest('tr').index();
if (i <= 8) {
setTimeout(function(){
if (winhor(row, col)) {
alert(m[row][col] + " Won!");
location.reload();
}
if (winver(row, col)) {
alert(m[row][col] + " Won!");
location.reload();
}
if (windiag(row, col)) {
alert(m[row][col] + " Won!Diag");
location.reload();
}
},100);
} else {
alert("Draw");
location.reload();
}
});
function winhor(row, col) {
var sym = m[row][col];
var val;
var check = true;
for (val = 0; val < 3; val++) {
if (sym != m[row][val]) {
check = false;
break;
}
}
return check;
}
function winver(row, col) {
var sym = m[row][col];
var val;
var check = true;
for (val = 0; val < 3; val++) {
if (sym != m[val][col]) {
check = false;
break;
}
}
return check;
}
function windiag(row, col) {
var sym = m[row][col];
var valr, valc;
var check = true;
if ((row != col) && (row + col != 2)) {
//alert("not 0 or 3 or 2");
return false;
} else if (row == col) {
for (valr = 0, valc = 0; valr < 3; valr++) {
if (sym != m[valr][valc]) {
//alert("not equal at "+valr+" "+valc);
check = false;
break;
}
valc++;
}
}
if (row + col == 2) {
check = true;
for (valr = 0, valc = 2; valr < 3; valr++) {
if (sym != m[valr][valc]) {
//alert("not equal at "+valr+" "+valc);
check = false;
break;
}
valc--;
}
}
return check;
}
td {
height: 50px;
width: 50px;
font-size=30px;
}
img{
width: 50px;
height: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<table border="1">
<tr>
<td class="game"></td>
<td class="game"></td>
<td class="game"></td>
</tr>
<tr>
<td class="game"></td>
<td class="game"></td>
<td class="game"></td>
</tr>
<tr>
<td class="game"></td>
<td class="game"></td>
<td class="game"></td>
</tr>
</table>
You have to ask your code to wait for the image to be loaded before displaying the alert message:
var img = document.createElement("img");
img.src = "file:///c:/NNK/TicTacToe/TicTacToeV1/WebContent/WEB-INF/tictactoeO.jpe";
img.onload = function() {
checkIfYouWon();
}
$(this).prepend(img);
The checkIfYouWon() function is where you do the tests of victory.
Here is the code:
$(".game").click(function() {
var img = document.createElement("img");
img.onload = function() {
checkIfYouWon();
}
var col = $(this).index();
var row = $(this).closest('tr').index();
if (i % 2 == 0) {
img.src = "file:///c:/NNK/TicTacToe/TicTacToeV1/WebContent/WEB-INF/tictactoeO.jpe";
m[row][col] = 'O';
} else {
img.src = "file:///c:/NNK/TicTacToe/TicTacToeV1/WebContent/WEB-INF/tictactoeX.jpe";
m[row][col] = 'X';
}
$(this).prepend(img);
$(this).off();
i++;
function checkIfYouWon(){
var col = $(this).index();
var row = $(this).closest('tr').index();
if (i <= 8) {
if (winhor(row, col)) {
alert(m[row][col] + " Won!");
location.reload();
}
if (winver(row, col)) {
alert(m[row][col] + " Won!");
location.reload();
}
if (windiag(row, col)) {
alert(m[row][col] + " Won!Diag");
location.reload();
}
} else {
alert("Draw");
location.reload();
}
}
});

Adding functionality for using the up and down arrow keys to select a table row

I need the help of an expert on my question below as it exceed and goes well beyond the level of knowledge that I have for programming in JavaScript.
Given the existing JavaScript coding below, how can I piggy back and add onto the existing coding so as to add functionality for a user to use their up and down arrow keys to scroll through the table, while they are scrolling through (btw the header column exempt) it would highlight the selected row and change its row color.
A point to note that if an existing table row is selected, and I hit my up or down arrow key, it would just move to and highlight the previous and next row. Some logic here is that I am guessing that one would need to find the row index to do this. Like I said, it is well beyond what I know how to do.
Much thanks and a huge appreciation for all your help.
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
tr.normal td {
color: black;
background-color: white;
}
tr.highlighted td {
color: white;
background-color: red;
}
</style>
</head>
<body>
<div id="results" class="scrollingdatagrid">
<table id="mstrTable" cellspacing="0" border="1">
<thead>
<tr>
<th>File Number</th>
<th>Date1</th>
<th>Date2</th>
<th>Status</th>
<th>Num.</th>
</tr>
</thead>
<tbody>
<tr>
<td>KABC</td>
<td>09/12/2002</td>
<td>09/12/2002</td>
<td>Submitted</td>
<td>1</td>
</tr>
<tr>
<td>KCBS</td>
<td>09/11/2002</td>
<td>09/11/2002</td>
<td>Lockdown</td>
<td>2</td>
</tr>
<tr>
<td>WFLA</td>
<td>09/11/2002</td>
<td>09/11/2002</td>
<td>Submitted</td>
<td>3</td>
</tr>
<tr>
<td>WTSP</td>
<td>09/15/2002</td>
<td>09/15/2002</td>
<td>In-Progress</td>
<td>4</td>
</tr>
</tbody>
</table>
</div>
<script type="text/javascript">
(
function() {
var trows = document.getElementById("mstrTable").rows;
for (var t = 1; t < trows.length; ++t) {
trow = trows[t];
trow.className = "normal";
trow.onclick = highlightRow;
}//end for
function highlightRow() {
for ( var t = 1; t < trows.length; ++t ) {
trow = trows[t];
if (trow != this) { trow.className = "normal" }
}//end for
this.className = (this.className == "highlighted")?"normal":"highlighted";
}//end function
}//end function
)();//end script
</script>
</body>
</html>
This is certainly not optimal, but as you're not using jQuery (or a similar library) you've incurred a lot of cross browser overhead. This should be backwards compatible up to IE8.
Live Demo
HTML
Only change here is the addition of the tabindex
<table tabindex='0' id="mstrTable" cellspacing="0" border="1">
JS
//From: http://forrst.com/posts/JavaScript_Cross_Browser_Event_Binding-yMd
var addEvent = (function( window, document ) {
if ( document.addEventListener ) {
return function( elem, type, cb ) {
if ( (elem && !elem.length) || elem === window ) {
elem.addEventListener(type, cb, false );
}
else if ( elem && elem.length ) {
var len = elem.length;
for ( var i = 0; i < len; i++ ) {
addEvent( elem[i], type, cb );
}
}
};
}
else if ( document.attachEvent ) {
return function ( elem, type, cb ) {
if ( (elem && !elem.length) || elem === window ) {
elem.attachEvent( 'on' + type, function() { return cb.call(elem, window.event) } );
}
else if ( elem.length ) {
var len = elem.length;
for ( var i = 0; i < len; i++ ) {
addEvent( elem[i], type, cb );
}
}
};
}
})( this, document );
//derived from: http://stackoverflow.com/a/10924150/402706
function getpreviousSibling(element) {
var p = element;
do p = p.previousSibling;
while (p && p.nodeType != 1);
return p;
}
//derived from: http://stackoverflow.com/a/10924150/402706
function getnextSibling(element) {
var p = element;
do p = p.nextSibling;
while (p && p.nodeType != 1);
return p;
}
;(function() {
var trows = document.getElementById("mstrTable").rows;
for (var t = 1; t < trows.length; ++t) {
trow = trows[t];
trow.className = "normal";
trow.onclick = highlightRow;
}//end for
function highlightRow() {
for ( var t = 1; t < trows.length; ++t ) {
trow = trows[t];
if (trow != this) { trow.className = "normal" }
}//end for
this.className = (this.className == "highlighted")?"normal":"highlighted";
}//end function
addEvent(document.getElementById('mstrTable'), 'keydown', function(e){
var key = e.keyCode || e.which;
if((key === 38 || key === 40) && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey){
var highlightedRows = document.querySelectorAll('.highlighted');
if(highlightedRows.length > 0){
var highlightedRow = highlightedRows[0];
var prev = getpreviousSibling(highlightedRow);
var next = getnextSibling(highlightedRow);
if(key === 38 && prev && prev.nodeName === highlightedRow.nodeName){//up
highlightedRow.className = 'normal';
prev.className = 'highlighted';
} else if(key === 40 && next && next.nodeName === highlightedRow.nodeName){ //down
highlightedRow.className = 'normal';
next.className = 'highlighted';
}
}
}
});
})();//end script
I don't think this actually needs to be that long--you just need to keep the index of the currently highlighted row.
This has only been tested on Chrome (I don't have IE), but it should work.
(function() {
/**
* Gets the tr at the specified row or column
*/
var tbody = document.getElementsByTagName('tbody')[0];
function getRow(row) {
return tbody.getElementsByTagName('tr')[row];
}
// store these so we won't have to keep recalculating
var numRows = tbody.getElementsByTagName('tr').length;
// index of the currently highlighted row
var curRow = 0;
// highlight the initially highlighted cell
getRow(curRow).className = 'highlighted';
// listen for keydown event
if (addEventListener) {
window.addEventListener('keydown',keydownHandler, false);
} else if (window.attachEvent) {
window.attachEvent('onkeydown', keydownHandler);
}
// handle keydown event
function keydownHandler (evt) {
// return the old cell to normal
getRow(curRow).className = 'normal';
// increment/decrement the position of the current cell
// depending on the key pressed
if (evt.keyCode == 38 && curRow > 0) // up
curRow--;
else if (evt.keyCode == 40 && curRow < numRows-1) // down
curRow++;
// update the new cell
getRow(curRow).className = 'highlighted';
}
})();//end script
I have create a demo using JQuery here on JSBin
In general, we have 2 task:
highlight selected row
choose next/prev row
To highlight the "clicked" row, I use this code
$("#mstrTable tr").click(function(evt){
var element = $(evt.target);
var tableElement = element.parents('table');
tableElement.find('tr').removeClass('highlighted');
element.parents('tr').addClass('highlighted');
});
To choose next/prev row, I use jQuery tree traversal function with some exception when there is no tr inside your tbody. Note that keyCode of left, right, up, down arrow are 37, 39, 38, 40 respectively.
$(document).keypress(function(evt){
var highlightedRow = $("#mstrTable .highlighted");
if (highlightedRow.length > 0) // table cell is selected
{
var tbodyElement = highlightedRow.parents('tbody');
var trElements = tbodyElement.find('tr');
var nextElement = highlightedRow.next('tr');
var prevElement = highlightedRow.prev('tr');
trElements.removeClass("highlighted");
switch(evt.keyCode)
{
case 40:
if(nextElement.length)
{
nextElement.addClass('highlighted');
}
else if (trElements.length)
{
$(trElements[0]).addClass('highlighted');
}
break;
case 38:
if(prevElement.length)
{
prevElement.addClass('highlighted');
}
else if (trElements.length)
{
$(trElements[trElements.length - 1]).addClass('highlighted');
}
break;
}
}
});
Here is the complete solution which selects rows in table just like a windows file selection works.
add class multiSelect to you table and then place this code in a JS file
$(document).ready(function() {
var selectionPivot;
// code for selected rows.
$('.multiSelect tbody').on( 'click', 'tr', function (e) {
var tbodyElement = $(this).parents('tbody');
var trElements = tbodyElement.find('tr');
if(!e.ctrlKey && (!e.shiftKey) ){
trElements.removeClass("row_selected");
selectionPivot=$(this);
}
if(e.shiftKey){
var bot = Math.min(selectionPivot[0].rowIndex, $(this)[0].rowIndex);
var top = Math.max(selectionPivot[0].rowIndex, $(this)[0].rowIndex);
trElements.removeClass("row_selected");
for(var i=bot; i<=top; i++){
trElements[i-1].className+=" row_selected";
}
}
else {
selectionPivot=$(this);
trElements.removeClass("focus");
$(this).addClass('focus');
if ( $(this).hasClass('row_selected') ) {
$(this).removeClass('row_selected');
}
else {
$(this).addClass('row_selected');
}
}
});
$(document).keypress(function(evt){
if(evt.shiftKey){
var highlightedRow = $(".multiSelect .focus");
if (highlightedRow.length > 0) // table cell is selected
{
var tbodyElement = highlightedRow.parents('tbody');
var trElements = tbodyElement.find('tr');
var nextElement = highlightedRow.next('tr');
var prevElement = highlightedRow.prev('tr');
trElements.removeClass("focus");
switch(evt.keyCode)
{
case 40:
if(nextElement.length)
{
nextElement.addClass('row_selected');
nextElement.addClass('focus');
}
else if (trElements.length)
{
$(trElements[0]).addClass('row_selected');
$(trElements[0]).addClass('focus');
}
break;
case 38:
if(prevElement.length)
{
prevElement.addClass('row_selected');
prevElement.addClass('focus');
}
else if (trElements.length)
{
$(trElements[trElements.length - 1]).addClass('row_selected');
$(trElements[trElements.length - 1]).addClass('focus');
}
break;
}
}
}
});
});

Categories

Resources