Recursive function keeps looping infinitely in minimax algorithm code - javascript

I'm trying to write the minimax algorithm in Javascript for a tic-tac-toe game project. It's a person vs computer game. I want to find the best move for the computer but I keep getting the following error on the Chrome console: Uncaught RangeError: Maximum call stack size exceeded.
I've tried to solve the problem many different ways but it has become hard for me to debug.
If anyone could help it would be appreciated, thanks in advance.
Here is my sample code:
let board_index = ["X", 1, 2, 3, "O", 5, 6, "X", 8]; // original board with the somes indexies played
let ai = "X";
let hu = "O";
// filter the available boxes in the board
function emptySpot(board) {
return board.filter(st => st != "O" && st != "X");
}
// winning combinations using the board indexies
function evaluate(board, player) {
if (
(board[0] == player && board[1] == player && board[2] == player) ||
(board[3] == player && board[4] == player && board[5] == player) ||
(board[6] == player && board[7] == player && board[8] == player) ||
(board[0] == player && board[3] == player && board[6] == player) ||
(board[1] == player && board[4] == player && board[7] == player) ||
(board[2] == player && board[5] == player && board[8] == player) ||
(board[0] == player && board[4] == player && board[8] == player) ||
(board[2] == player && board[4] == player && board[6] == player)
)
{
if(player == ai) {
return 10;
}
else
if(player == hu) {
return -10;
}
return 0;
}
}
// minimax algorithm
function minimax(board, depth, isMax) {
// available spots in board
let avail = emptySpot(board);
// evaluate score on board
let score = evaluate(board);
// check and return a value for terminal states such as win, lose, and tie
if(score == 10 || score == -10) {
return score;
}
else if(avail == []) {
return 0;
}
if(isMax) {
let bestScore = -1000;
for(let i = 0; i < avail.length; i++) {
let index = avail[i];
// make move
avail[i] = ai;
// call minimax recursively and choose the maximum value
bestScore = minimax(board, depth+1, !isMax);
// undo the move
avail[i] = index;
}
return bestScore;
}
else {
let bestScore = 1000;
for(let i = 0; i < avail.length; i++) {
let index = avail[i];
// make move
avail[i] = hu;
// call minimax recursively and choose the minimum value
bestScore = minimax(board, depth+1, !isMax);
// undo the move
avail[i] = index;
}
return bestScore;
}
}
// finding the best possible move and return it
function findBestMove(board) {
let bestVal = -1000;
let bestMove = -1;
let avail = emptySpot(board);
for(let i = 0; i < avail.length; i++) {
// save index
let index = avail[i];
// compute the evalutation for this move
let moveVal = minimax(board, 0, false);
// undo move
avail[i] = index;
if(moveVal > bestVal){
bestVal = moveVal;
bestMove = avail[i];
}
}
console.log(bestVal);
console.log(bestMove);
}
let bestSpot = findBestMove(board_index);

Related

Need help finding the problem in my tic-tac-toe minimax algorithm

I've been building out my tic-tac-toe project for a while now, and I've been running into issues with the unbeatable AI implementation using minimax.
I'm at the point now where the game is almost completely functioning, just that I have one particular square on the grid which is not working as intended. As in, it seems to account for all the outcomes apart from the square at index [8] from the array. It seems to avoid placing itself there or calculating the correct result from it.
const gameBoard = (() => {
//stores current gameboard as an array
let array = ['','','','','','','','',''];
let currentPlayer;
//clears the gameBoard display
button.addEventListener('click', () => {
clearDisplay();
});
//game display that renders the contents of array to gameboard
const display = (index, name) => {
//renders players click to the board, taking in the index of the square
gameBoard.array.splice(index, 1, name.team);
innerAnswerText[index].innerText = gameBoard.array[index];
}
//clears the array and display
const clearDisplay = () => {
gameBoard.array = ['','','','','','','','',''];
innerAnswerText.forEach(text => text.innerText = '');
}
return { display, array, clearDisplay, currentPlayer }
})();
//contains all of our game logic
const game = (() => {
//starts our game and controls the flow of player vs ai
const startGame = (index) => {
//game display that takes in the players click index and renders the contents of array to gameboard
gameBoard.display(index, ryan);
gameBoard.currentPlayer = 'X';
if (checkWinner(gameBoard.currentPlayer) !== null) {
console.log(gameBoard.currentPlayer + ' has won the round!');
return;
}
//computers turn that takes a random choice from the indexes that are empty and prints it to the board
let compMove;
let bestScore = -Infinity;
gameBoard.array.forEach((item,i) => {
if (gameBoard.array[i] == '') {
gameBoard.array[i] = 'O';
let score = minimax(gameBoard.array, 0, false);
gameBoard.array[i] = '';
if (score > bestScore) {
bestScore = score;
compMove = i;
}
}
});
gameBoard.display(compMove, computer);
gameBoard.currentPlayer = 'O';
if (checkWinner(gameBoard.currentPlayer) !== null) {
console.log(gameBoard.currentPlayer + ' has won the round!');
return;
}
}
//loop through our html boxes and apply event listeners to each square that will initiate the players round
boxes.forEach((square, i) => {
square.onclick = () => {
startGame(i);
}
});
//checks for a win or a tie using current player variable to check whose turn it is
const checkWinner = (player) => {
let winner = null;
let a = gameBoard.array;
let openSpaces = 0;
//horizontal win check
if (a[0] == player && a[1] == player && a[2] == player) {
winner = player;
}
if (a[3] == player && a[4] == player && a[5] == player) {
winner = player;
}
if (a[6] == player && a[7] == player && a[8] == player) {
winner = player;
}
//vertical win check
if (a[0] == player && a[3] == player && a[6] == player) {
winner = player;
}
if (a[1] == player && a[4] == player && a[7] == player) {
winner = player;
}
if (a[2] == player && a[5] == player && a[8] == player) {
winner = player;
}
//diagonal win check
if (a[0] == player && a[4] == player && a[8] == player) {
winner = player;
}
if (a[2] == player && a[4] == player && a[6] == player) {
winner = player;
}
for (let i=0; i < 8; i++) {
if (gameBoard.array[i] == '') {
openSpaces++;
}
}
if (winner == null && openSpaces == 0) {
return 'tie';
}
return winner;
}
let scores = {
X: -10,
O: 10,
tie: 0
}
const minimax = (board, depth, maximizingPlayer) => {
let result = checkWinner(gameBoard.currentPlayer);
if (result !== null) {
return scores[result];
}
if (maximizingPlayer) {
let bestScore = -Infinity;
for (let i = 0; i < 8; i++) {
if (board[i] == '') {
board[i] = 'X';
let score = minimax(board, depth + 1, false);
board[i] = '';
bestScore = Math.max(score, bestScore);
}
}
return bestScore;
} else {
let bestScore = Infinity;
for (let i = 0; i < 8; i++) {
if (board[i] == '') {
board[i] = 'O';
let score = minimax(board, depth + 1, true);
board[i] = '';
bestScore = Math.min(score, bestScore);
}
}
return bestScore;
}
}
return { startGame, checkWinner }
})();
//factory function that produces our player object taking in name and team
const player = (name, team) => {
return { name, team };
}
const ryan = player('ryan', 'X');
const computer = player('computer', 'O');
})();
I know there is a lot of code here, I'm thinking the problem lies somewhere around our for each or for loops inside the minimax recursion, but I'm unsure. I'll provide my codepen below so you can see the actual game working and you'll see what I mean about that 9th square which isn't working as intended.
https://codepen.io/itswakana/pen/gOeMrym?editors=1111
I appreciate any help you can give! Thanks.
In your Minimax function you write for (let i = 0; i < 8; i++) { in two places. Shouldn't that 8 be a 9, or put a <= instead of <? Right now you are just checking for index 0 to 7 which is the first 8 squares.

javascript imports are real-time?

well, i'm trying to do a snake game using basic imports, but at some time, i got stuck, because of a javascript assignment.
i assigned the value the oldSnake as the value of the snake before its changes.
but as i get into the forEach, the value already assigned changes and i don't know why.
Before getting into the forEach
After getting into the forEach
i searched about it, and didn't get anywhere, Does someone know what's happening?
import { snake } from "/snake.js";
let lastDirection = {y: 1, x:0};
export function updateGame(direction){
let oldSnake = snake;
if (!(direction.y == 0 && direction.x == 0)){
if((lastDirection.y == 1 && direction.y == -1 || lastDirection.y == -1 && direction.y == 1)
||(lastDirection.x == 1 && direction.x == -1 || lastDirection.x == -1 && direction.x == 1)){
direction = lastDirection;
}
snake.forEach((snakeSlice, index)=>{
lastDirection = direction;
if (index === 0){
snakeSlice.ypos+= direction.y;
snakeSlice.xpos+= direction.x;
}
else{
snakeSlice.ypos = oldSnake[index-1].ypos;
snakeSlice.xpos = oldSnake[index-1].xpos;
}
});
}
}

Interesting if/else if conditional chain problem

I am having a hard time figuring out why this code is working the way it is. Below, I have all of the code that produces my tic tac toe game. The game works as intended until we come to a full board that SHOULD result in a win for either 'O' or 'X'. My current logic sometimes will work as intended and pick the correct winner but a majority of the time, it will produce a 'DRAW', even if the last move on the last square should result in a win. Given the if/else if chain, I don't see how that would work? The applicable if/else if statement is the second set.
Here is my code:
$(document).ready(function() {
let addX = "<h1>X</h1>"
let addO = "<h1>O</h1>"
let turn = []
let board = [$("#one"), $("#two"), $("#three"), $("#four"), $("#five"),$("#six"), $("#seven"), $("#eight"), $("#nine")]
let combos = [[0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [6,4,2]]
for (let i = 0; i < board.length; i++){
board[i].click(function(){
if (turn.length === 0){
turn.push(board.indexOf(board[i].html(addX)) + "X")
} else if (turn.length % 2 !== 0 && board[i].html() === ''){
turn.push(board.indexOf(board[i].html(addO)) + "O")
} else if (turn.length % 2 === 0 && board[i].html() === ''){
turn.push(board.indexOf(board[i].html(addX)) + "X")
}
for(let i = 0; i < combos.length; i++){
if (turn.includes(combos[i][0] + 'O') && turn.includes(combos[i][1] + 'O') && turn.includes(combos[i][2] + 'O') ){
alert('O IS WINNER!')
setTimeout(function() {$("#ttt_table tbody tr td").html(""); }, 1500);
turn.length = 0
} else if(turn.includes(combos[i][0] + 'X') && turn.includes(combos[i][1] + 'X') && turn.includes(combos[i][2] + 'X') ){
alert('X IS WINNER!')
setTimeout(function() {$("#ttt_table tbody tr td").html(""); }, 1500);
turn.length = 0
break
} else if (turn.length === 9){
alert('DRAW!')
setTimeout(function() {$("#ttt_table tbody tr td").html(""); }, 1500);
turn.length = 0
}
}
})
}
});
Here is a codepen to test out the game itself:
https://codepen.io/tylerp33/pen/NeOxyY
Basically, shouldn't the game see there's a winning combo in:
else if(turn.join("").includes(combos[i][0] + 'X') && turn.join("").includes(combos[i][1] + 'X') && turn.join("").includes(combos[i][2] + 'X')
before the last
else if (turn.length === 9) produces a 'DRAW'?
Thanks for your help!
Without changing or doing much refactoring, adding this to the last condition worked. Basically, making the if/else run through all the combos before deciding on a draw did the trick. Thanks for all the help!
else if (turn.length === 9 && combos[i] === combos[combos.length - 1]) {
alert('DRAW!')
setTimeout(function() {$("#ttt_table tbody tr td").html(""); }, 1500);
turn.length = 0
}

The Conditions In My First While Loop Seems To Not Be Working Properly

<script>
function Play() {
var guesses = 1
var number = 0
var points = 0
var cp = 0
var level = " "
var x = 0
while (level != "a" || "A" || "b" || "B" || "c" || "C" || "d" || "D"){
level = prompt("Select Your Difficulty");
if (level == "a" || "A" || "b" || "B" || "c" || "C" || "d" || "D") {
if (level == "a" || level == "A") {
x = 30;
}
else if (level == "b" || level == "B") {
x = 50;
}
else if (level == "c" || level == "C") {
x = 70;
}
else if (level == "d" || level == "D") {
x = 100;
}
}
else if (level != "a" || "A" || "b" || "B" || "c" || "C" || "d" || "D") {
if (level == null) {
return;
}
else {
alert("You Went Sphagetti On Your Keyboard And Missed");
}
}
}
var answer = Math.floor(Math.random() * x) + 1;
number = prompt("What Is Your First Guess?");
if (number == null) {
return;
}
//The Beginning Of The While Loop
while (guesses <= 4) {
//Give The User A Point And Restart The Guesses
//Generate A New Number For Them To Guess
if (number == answer) {
alert("You Guessed Correctly! You Get One Point!");
alert("The Number Will Now Change, But Will Still Be Within 1-30");
guesses = 1;
points = points + 1;
answer = Math.floor(Math.random() * x) + 1;
number = prompt("What Is Your Guess For The New Number?");
if (number == null){
return;
}
//If The Number The User Guessed Equals The Answer
//Go Back To The Start Of The Loop
else if (number == answer) {
continue;
}
}
if (number < answer) {
alert("Wrong! The Number Is To Low! NACHO CHEESE");
}
else if (number > answer) {
alert("Wrong! The Number Is To High! YOU ARE SHELLFISH!");
}
number = prompt("You've Made " + guesses + " Guess(es)");
if (number == null) {
return;
}
if (number == answer) {
continue;
}
else if (number != answer) {
guesses = guesses + 1;
}
}
//If The User Gets Wrong On All 5 Guesses
//Tell Them They Lost And Give Them The Answer
//Display Their Total Points
alert("You Have Guessed Incorrectly 5 Times. The Number Was " + answer);
cp = points.toString();
document.getElementById("points").innerHTML= "Point Streak: " + cp;
}
</script>-
Hello, I was wondering why the conditions in my first while loop do not seem to be working properly. When I enter in a letter that should break out of the loop since the condition is while it is not equal to one of these letters, it just ends up asking me to again to ("Select Your Difficulty"). Before it was also giving me the "You Went Spaghetti On Your Keyboard And Missed") When I entered in a letter that should break out of the loop... If I am not mistaken if should not even be entering that else if in the first place if I enter in one of those letters right? Please guide me where I went wrong.

Minimax implementation of TicTacToe in ES6 not returning appropriate results

I've written a minimax implementation for playing tic-tac-toe.This is a standalone function that accepts the game state and turn and returns best position.
Here's the code:
function minimax(grid,isMaximizingPlayer,depth){
let turnOf = (isMaximizingPlayer)?'O':'X';
console.log("Playing turn of : "+turnOf);
console.log("Starting minimax # depth: "+depth);
let vacancies = getVacantPlaces(grid);
let winner = calculateWinner(grid);
if(vacancies.length === 0){
if((isMaximizingPlayer && winner==='O') || (!isMaximizingPlayer &&
winner==='X'))
return {score: 10,position: -1};
else if((isMaximizingPlayer && winner==='X') || (!isMaximizingPlayer &&
winner==='O'))
return {score: -10,position: -1};
else if(null===winner)
return {score: 0,position: -1};
}
let moves = [];
for(let i=0;i<vacancies.length;i++){
let move = {position: -1,score: 0};
grid[vacancies[i]] = (isMaximizingPlayer) ? 'O' : 'X';
move.score = minimax(grid,!isMaximizingPlayer,depth+1).score;
if((isMaximizingPlayer && 10===move.score) ||
(!isMaximizingPlayer && -10===move.score)){
move.position = vacancies[i];
console.log("Pruning other choices, best score got #:
"+JSON.stringify(move));
return move;
}
//reset the grid
grid[vacancies[i]] = null;
moves.push(move);
}
let bestMove = {position: -1,score: 0};
if(isMaximizingPlayer){
//start with minimum score and select max of moves
bestMove.score = -10;
bestMove = moves.reduce((previousBest,currentMove) => {
return (currentMove.score > previousBest.score) ? currentMove :
previousBest;
},bestMove);
}else {
//start with maximum score and select min of moves
bestMove.score = 10;
bestMove = moves.reduce((previousBest,currentMove) => {
return (currentMove.score < previousBest.score) ? currentMove :
previousBest;
},bestMove);
}
console.log("Best move for iteration : "+ JSON.stringify(bestMove));
return bestMove;
}
//Testing input
var gameState = [null,null,'X',
'O',null,'X',
null,null,null];
console.log(JSON.stringify(minimax(gameState,true,1)));
//OUTPUTS: {"position":7,"score":10}
//EXPECTED: {"position":8,"score":0}
Here's the jsfiddle for the above code along with the helper functions for getting vacant places and calculating winner.Tried hard but couldn't figure out the missing part.

Categories

Resources