Solving TicTacToe with minimax algorithm in Javascript - 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>

Related

find the overlap between two strings

I have a string and need to check with and get whether the following strings overlap with the start and end of my target string:
target string: "click on the Run"
search strings: "the Run button to", "code and click on"
Apparently:
"the Run button to" is overlapped at the end of target "click on the Run"
"code and click on" is overlapped at the start of target "click on the Run"
Both, "the Run" and "click on" will be the desired results.
I have come up with a function to check and get the overlapped results for the cases at the start and at the end separately.
Question:
But my code could not be able to get the expected results only if I know how the search string overlapped with the target string in the very first place. And how can I combine the searched results in one go as well?
function findOverlapAtEnd(a, b) {
if (b.length === 2) {
return "";
}
if (a.indexOf(b) >= 0) {
return b;
}
if (a.endsWith(b)) {
return b;
}
return findOverlapAtEnd(a, b.substring(0, b.length - 1));
}
function findOverlapAtStart(a, b) {
if (b.length === 2) {
return "";
}
if (a.indexOf(b) >= 0) {
return b;
}
if (a.startsWith(b)) {
return b;
}
return findOverlapAtStart(a, b.substring(1));
}
console.log(findOverlapAtEnd("click on the Run", "the Run button to"))
console.log(findOverlapAtStart("click on the Run", "code and click on"))
edited:
case in the middle is also considered, e.g.:
target string: "click on the Run"
search strings: "on the"
Return value: "on the"
You may try this
function findOverlapAtEnd(a, b, min) {
if (b.length <= min) {
return '';
}
if (a.indexOf(b) >= 0) {
return b;
}
if (a.endsWith(b)) {
return b;
}
return findOverlapAtEnd(a, b.substring(0, b.length - 1), min);
}
function findOverlapAtStart(a, b, min) {
if (b.length <= min) {
return '';
}
if (a.indexOf(b) >= 0) {
return b;
}
if (a.startsWith(b)) {
return b;
}
return findOverlapAtStart(a, b.substring(1), min);
}
const GetOverlappingSection = (target, search, min) => {
if (target.length < search.length) {
const tmp = target;
target = search;
search = tmp;
}
let overlap1 = findOverlapAtStart(target, search, min);
if (overlap1.length === 0) {
overlap1 = findOverlapAtEnd(target, search, min);
}
return overlap1;
};
const removeEmptyKeyword = overlap => {
let tmpFinaloverlap = [];
overlap.forEach((key, idx) => {
if (!(key.trim().length === 0)) {
tmpFinaloverlap = [...tmpFinaloverlap, key];
}
});
return tmpFinaloverlap;
};
// let overlap = ['click on','the Run']
const GetOverlappingOfKeyowrd1And2 = (keywordSet1, keywordSet2,min) => {
let resultSetoverlap = [];
let tmpresultSetoverlap = [];
keywordSet1.forEach(key =>
keywordSet2.forEach(k2 => {
tmpresultSetoverlap = [
...tmpresultSetoverlap,
GetOverlappingSection(key, k2, min),
];
})
);
// get the resultSetoverlap
tmpresultSetoverlap.forEach(element => {
if (element.length > 0) {
resultSetoverlap = [...resultSetoverlap, element];
}
});
return resultSetoverlap;
};
const min = 2;
//To handle overlapping issue in overlapping set, that casuing
overlap.forEach((key, idx) => {
if (idx < overlap.length - 1) {
for (let i = idx + 1; i < overlap.length; i++) {
console.log(`key: ${key}`);
console.log(`search: ${overlap[i]}`);
let overlapSection = GetOverlappingSection(key, overlap[i], min);
if (overlapSection.length > 0) {
console.log(`overlapSection: ${overlapSection}`);
overlap[idx] = overlap[idx].replace(overlapSection, '');
}
}
}
});
overlap = removeEmptyKeyword(overlap);
console.log(overlap);
overlap.forEach(key => {
keywordSet2 = keywordSet2.map((k1, idx) => {
console.log(`checking overlap keyword:'${key}' in '${k1}'`);
return k1.replace(key, '');
});
});
overlap.forEach(key => {
keywordSet1 = keywordSet1.map((k1, idx) => {
console.log(`checking overlap keyword:'${key}' in '${k1}'`);
return k1.replace(key, '');
});
});
keywordSet2 = removeEmptyKeyword(keywordSet2);
keywordSet1 = removeEmptyKeyword(keywordSet1);
overlap.forEach(key => {
text = text.replace(key, `$#k1k2$&$`);
});
keywordSet1.forEach(key => {
text = text.replace(key, `$#k1$&$`);
});
keywordSet2.forEach(key => {
text = text.replace(key, `$#k2$&$`);
});
console.log(`ResultSetoverlap after processing:${text}`);
Because I need to decompress and I find these logic puzzles fun, here's my solution to the problem...
https://highdex.net/begin_end_overlap.htm
You can view source of the page to see JavaScript code I used. But just in case I ever take that page down, here's the important function...
function GetOverlappingSection(str1, str2, minOverlapLen = 4) {
var work1 = str1;
var work2 = str2;
var w1Len = work1.length;
var w2Len = work2.length;
var resultStr = "";
var foundResult = false;
var workIndex;
if (minOverlapLen < 1) { minOverlapLen = 1; }
else if (minOverlapLen > (w1Len > w2Len ? w2Len : w1Len)) { minOverlapLen = (w1Len > w2Len ? w2Len : w1Len); }
//debugger;
//we have four loops to go through. We trim each string down from each end and see if it matches either end of the other string.
for (var i1f = 0; i1f < w1Len; i1f++) {
workIndex = work2.indexOf(work1);
if (workIndex == 0 || (workIndex != -1 && workIndex == w2Len - work1.length)) {
//we found a match!
foundResult = true;
resultStr = work1;
break;
}
work1 = work1.substr(1);
if (work1.length < minOverlapLen) { break; }
}
if (!foundResult) {
//debugger;
//reset the work vars...
work1 = str1;
for (var i1b = 0; i1b < w1Len; i1b++) {
workIndex = work2.indexOf(work1);
if (workIndex == 0 || (workIndex != -1 && workIndex == w2Len - work1.length)) {
//we found a match!
foundResult = true;
resultStr = work1;
break;
}
work1 = work1.substr(0, work1.length - 1);
if (work1.length < minOverlapLen) { break; }
}
}
if (!foundResult) {
//debugger;
//reset the work vars...
work1 = str1;
for (var i2f = 0; i2f < w2Len; i2f++) {
workIndex = work1.indexOf(work2);
if (workIndex == 0 || (workIndex != -1 && workIndex == w1Len - work2.length)) {
//we found a match!
foundResult = true;
resultStr = work2;
break;
}
work2 = work2.substr(1);
if (work2.length < minOverlapLen) { break; }
}
}
if (!foundResult) {
//debugger;
//reset the work vars...
work2 = str2;
for (var i2b = 0; i2b < w2Len; i2b++) {
workIndex = work1.indexOf(work2);
if (workIndex == 0 || (workIndex != -1 && workIndex == w1Len - work2.length)) {
//we found a match!
foundResult = true;
resultStr = work2;
break;
}
work2 = work2.substr(0, work2.length - 1);
if (work2.length < minOverlapLen) { break; }
}
}
return resultStr;
}
Hopefully that's helpful.

How to stop loop from executing again after it has already finished iterating?

I'm trying to build a calculator, and I used objects to store both the operator and the numbers put into the calculator. And then the loop in the evaluateOperation function takes the operator in the object along with the first value 'result' value and the second value and operates these values using the operate function.
The issue is, as soon as the loop is complete and exits, it loops again before exiting finally. Details can be found below, detailed comments on each part of the code.
const number_buttons = document.querySelectorAll('.number');
const input = document.getElementById('calc_display');
const op = document.querySelectorAll('.operator');
const multiplication = document.querySelector('#multiply');
const division = document.querySelector('#divide');
const addition = document.querySelector('#add');
const subtraction = document.querySelector('#subtract');
const evaluate = document.querySelector('#evaluate');
const decimal = document.querySelector('#decimal button');
const clearAllData = document.querySelector('.clear button')
//number storage
let num_display = {
result: 0,
};
let operatorStorage = {};
let count = 0;
let operatorSequence = 0;
let iterable_for_post_result = 1;
let iterable_for_result = 0;
let operatorType;
let operationResult = 0;
let clicked = 1;
let decimalClicks = 0;
let called = 1;
//performs operation
function operate(operator, num1, num2) {
switch (operator) {
case '+':
console.log(Number((add(num1, num2)).toFixed(2)));
return Number((add(num1, num2)).toFixed(2));
break;
case '-':
console.log(Number((subtract(num1, num2)).toFixed(2)));
return Number((subtract(num1, num2)).toFixed(2));
break;
case '*':
console.log(Number((multiply(num1, num2)).toFixed(2)));
return Number((multiply(num1, num2)).toFixed(2));
break;
case '/':
console.log(Number((divide(num1, num2)).toFixed(2)));
return Number((divide(num1, num2)).toFixed(2));
break;
}
}
// When numbers are clicked eventlistener
number_buttons.forEach(el => {
el.addEventListener('click', () => {
if (el.textContent == '.') {
console.log('deci');
if (decimalClicks == 1) {
el.disabled = true;
decimalClicks--;
} else {
decimalClicks++;
el.disabled = false;
input.value += el.textContent;
if (count == 0) {
// When an operation is called before a number is entered result becomes 0.
num_display.result = Number(input.value);
} else if (count >= 1) {
num_display[`post_op_result ${count}`] = Number(input.value);
}
}
} else {
input.value += el.textContent;
if (count == 0) {
num_display.result = Number(input.value);
} else if (count >= 1) {
num_display[`post_op_result ${count}`] = Number(input.value);
}
console.log(num_display);
}
});
});
// Eventlistener when the '=' is clicked
//
evaluate.addEventListener('click', () => {
iterable_for_result;
iterable_for_post_result;
// Condition for if user does not click an operator
if (operatorType == undefined || isNaN(operatorType) == true && Object.keys(num_display).length == 1) {
return;
} else {
// Condition for if user does not input the first number
if (input.value == '') {
// if a user inputted the first number but did not enter an operator
if (operatorType == undefined || isNaN(operatorType) == true) {
operatorType = '+';
}
input.value = `${num_display['result']}`;
operationResult = Number(input.value);
console.log(operationResult);
return operationResult;
} else {
let errorMessage = evaluateOperation(iterable_for_result, iterable_for_post_result);
// if divide number by 0
if (errorMessage == Infinity) {
console.log('Bwana');
errorMessage = 'YERRRRR';
input.value = `${errorMessage}`;
errorMessage = 0;
operationResult = Number(input.value);
num_display['result'] = operationResult;
} else {
//If no problems with inputs, calculates and calls evaluateOperation function
input.value = `${evaluateOperation(iterable_for_result, iterable_for_post_result)}`;
//operationResult = Number(input.value);
//num_display['result'] = operationResult;
}
}
}
iterable_for_post_result++;
iterable_for_result++;
console.log(num_display);
});
// operator click event listener
op.forEach(btn => {
btn.addEventListener('click', () => {
console.log(operatorType);
if (clicked > 1) {
clicked -= 2;
count++;
clear();
} else if (clicked <= 1) {
count++;
decimal.disabled = false;
decimalClicks = 0;
clicked++;
clear();
}
})
});
//Stores the operator clicked into operator object
function operatorTypeFunction(operator) {
operatorSequence++;
operatorStorage[`Operator ${operatorSequence}`] = operator;
operatorType = operatorStorage[`Operator ${operatorSequence}`];
console.log(operatorStorage);
}
// clears only the inputted value
function clear() {
input.value = "";
}
function add(num1, num2) {
return num1 + num2;
}
function subtract(num1, num2) {
return num1 - num2;
}
function multiply(num1, num2) {
return num1 * num2;
}
function divide(num1, num2) {
return num1 / num2;
}
// second inputted value
function storedKeys(i) {
return num_display[Object.keys(num_display)[i]];
}
// first inputted value/ also stored as the final result of every operation performed
// i.e. firstvalue = 10; secondvalue = 12;
// newvalue = firstvalue + secondvalue;
// Make firstvalue = 0 and then...
// firstvalue += newvalue
function resultNum1(i) {
if (operationResult == 0 && Object.keys(num_display).length <= 2) {
console.log('result ' + num_display[Object.keys(num_display)[i]]);
return num_display[Object.keys(num_display)[i]];
} else {
if (isNaN(operationResult) == true) {
operationResult = 0;
return operationResult;
} else {
return num_display['result'];
}
}
}
function evaluateOperation(iterable_variable_1, iterable_variable_2) {
let postResultIterable = 0;
let resultNumIterable = -1;
if (Object.keys(num_display).length > 2) {
console.log('Run');
// Something wrong with iteration, it loops through everything when all numbers are calculated
// it does another full loop with the new calculation after exiting the loop
for (let i = 1; i <= Object.keys(operatorStorage).length; i++) {
// The logging are to help to see the result and what is being used in the operate func
console.log(i);
resultNumIterable++;
postResultIterable++;
console.log(operatorStorage[`Operator ${i}`]);
console.log(storedKeys(postResultIterable));
operationResult = operate(operatorStorage[`Operator ${i}`], resultNum1(resultNumIterable), storedKeys(postResultIterable));
num_display['result'] = 0;
num_display['result'] += operationResult;
}
return num_display['result'];
} else {
return operate(operatorType, resultNum1(iterable_variable_1), storedKeys(iterable_variable_2));
}
}
//Clears all data stored and the input
clearAllData.addEventListener('click', () => {
for (let key in num_display) {
for (let i = count; i >= 0; i--) {
num_display['result'] = 0;
delete num_display[`post_op_result ${i}`];
delete operatorStorage[`Operator ${i}`];
}
}
clear();
operationResult = 0;
count = 0;
clicked = 1;
decimalClicks = 0;
iterable_for_post_result = 1;
iterable_for_result = 0;
operatorSequence = 0;
});
body {
display: flex;
justify-content: center;
align-items: center;
height: 80vh;
}
.grid_buttons {
width: 200px;
height: 200px;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(5, 1fr);
justify-content: center;
border: 1px solid black;
}
#calc_display {
grid-column: 1/4;
outline: none;
border: none;
border-bottom: 1px solid black;
}
#calc_display:hover {
outline: none;
}
.equal {
grid-column: 2/4;
}
.grid_buttons div>* {
width: 100%;
}
button {
padding: 5px;
font-size: 0.8em;
background-color: white;
border: none;
}
button:hover {
background-color: rgb(53, 157, 223);
color: white;
cursor: pointer;
}
.equal button {
background-color: rgb(53, 157, 223);
color: white;
}
<div class="grid_buttons">
<input type="text" id="calc_display" value="" readonly>
<div id="decimal"><button class="number">.</button></div>
<div><button class="operator" id="multiply" onclick="operatorTypeFunction('*');">*</button></div>
<div><button class="operator" id="divide" onclick="operatorTypeFunction('/')">/</button></div>
<div><button class="operator" id="add" onclick="operatorTypeFunction('+')">+</button></div>
<div><button class="operator" id="subtract" onclick="operatorTypeFunction('-')">-</button></div>
<div class="clear"><button>AC</button></div>
<div id="7"><button class="number">7</button></div>
<div id="8"><button class="number">8</button></div>
<div id="9"><button class="number">9</button></div>
<div id="4"><button class="number">4</button></div>
<div id="5"><button class="number">5</button></div>
<div id="6"><button class="number">6</button></div>
<div id="1"><button class="number">1</button></div>
<div id="2"><button class="number">2</button></div>
<div id="3"><button class="number">3</button></div>
<div id="0"><button class="number">0</button></div>
<div class="equal"><button id="evaluate">=</button></div>
</div>
It's not looping twice, you're calling evaluateOperation() twice. First in
let errorMessage = evaluateOperation(iterable_for_result, iterable_for_post_result);
and again in
input.value = `${evaluateOperation(iterable_for_result, iterable_for_post_result)}`;
Instead of calling it again, just use the value you assigned from the first call:
input.value = error_message;

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>

Recursion issue with tic-tac-toe minimax

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.

Infinite recursion - Javascript minimax

I am trying to create a chess AI using the chess.js library. I am using the minimax solution with Alpha-Beta pruning, but for some reason, when the program runs it continues even after the depth reaches 0. Can anyone tell me why?
var Buddha = function() {
this.movehistory = 0;
this.color = "b";
this.opp = "w";
this.minimax = function(board, depth, alpha, beta) {
console.log(depth);
if(depth === 0 || board.game_over() === true) {
console.log("Depth == 0");
return [this.eval_board(board), null]
} else {
if(board.turn() === this.color) {
var bestmove = null
var possible_moves = board.moves()
for (index = 0; index < possible_moves.length; ++index) {
var new_board = new Chess(board.fen());
new_board.move(possible_moves[index])
var mini = this.minimax(new_board, --depth, alpha, beta)
var score = mini[0];
var move = mini[1];
if(score > alpha) {
alpha = score;
bestmove = possible_moves[index];
if(alpha >= beta) {
break;
}
}
}
return [alpha, bestmove]
} else if(board.turn() === this.opp) {
var bestmove = null
var possible_moves = board.moves()
for (index = 0; index < possible_moves.length; ++index) {
var new_board = new Chess(board.fen());
new_board.move(possible_moves[index])
var mini = this.minimax(new_board, --depth, alpha, beta)
var score = mini[0];
var move = mini[1];
if(score < beta) {
beta = score;
bestmove = possible_moves[index];
if(alpha >= beta) {
break;
}
}
}
return [beta, bestmove]
}
}
}
this.eval_board = function(board) {
if(board.in_check()) {
if(board.turn() == this.opp) {
return Number.POSITIVE_INFINITY;
} else {
return Number.NEGATIVE_INFINITY;
}
} else if(board.in_checkmate()) {
if(board.turn() == this.opp) {
return Number.POSITIVE_INFINITY;
} else {
return Number.NEGATIVE_INFINITY;
}
} else if(board.in_stalemate()) {
if(board.turn() == this.opp) {
return Number.POSITIVE_INFINITY;
} else {
return Number.NEGATIVE_INFINITY;
}
}
}
this.move = function(board) {
var bestmove = this.minimax(board, 1, Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY)
}
}
function minimax(board, depth, alpha, beta) {
if(depth === 0 …) { …
return …
} else {
…
for (index = 0; index < possible_moves.length; ++index) {
… minimax(new_board, --depth, alpha, beta)
// ^^
…
}
}
}
Here you're decrementing the depth in a loop. Use depth <= 0 for the base case, and/or pass depth - 1 as an argument or put the decrement statement before the loop.

Categories

Resources