"Maximum call stack size exceeded" error - javascript

So I'm trying to make a Tic Tac Toe game following this tutorial. When I run it and look at the Chrome dev tools, it says Uncaught RangeError: Maximum call stack size exceeded, and point me to the line of this function:
var State = function(oldState) {
this.turn = "";
this.oMovesCount = 0;
this.result = "still running";
this.board = [];
//get the information from the previous state to use it for following states
if (typeof oldState !== "undefined") {
var len = oldState.board.length;
this.board = new Array(len);
for (var i = 0; i < len; i++) {
this.board[i] = oldState.board[i];
}
this.oMovesCount = oldState.oMovesCount;
this.result = oldState.result;
this.turn = oldState.turn;
}
//change to X or O accordingly
this.advanceTurn = function() {
//Was it just X's turn? If so, change to O. If not, change to X.
this.turn = this.turn === "x" ? "o" : "x";
};
//checks for victory
this.result = "still running";
this.isTerminal = function() {
var B = this.board;
//check to see if there has been a victory
//check rows
for(var i = 0; i <= 6; i = i + 3) {
if(B[i] !== "E" && B[i] === B[i+1] && B[i+1] == B[i+2]) {
this.result = B[i] + " has won!";
return true;
}
}
//check columns
for(var i = 0; i <= 2 ; i++) {
if(B[i] !== "E" && B[i] === B[i+3] && B[i+3] === B[i+6]) {
this.result = B[i] + " has won!";
return true;
}
}
//check diagonals
for(var i = 0, j = 4; i <= 2 ; i = i + 2, j = j - 2) {
if(B[i] !== "E" && B[i] == B[i+j] && B[i+j] === B[i + 2*j]) {
this.result = B[i] + " has won!";
return true;
}
};
//if there have been no wins, check the number of empty cells
//if there are no empty cells, it's a draw
var available = this.emptyCells();
if (available.length == 0) {
this.result = "draw";
return true;
}
else {
return false;
}
};
//keeps track of how many empty cells there are on the board
this.emptyCells = function() {
var indxs = [];
for (var i = 0; i < 9; i++) {
if (this.board[i] === "E") {
indxs.push(i);
}
}
return indxs;
}
};
I don't understand why. Here's the full code, the error shows up when you click Play then OK then on one of the cells. Here it is hosted on another site if it helps.
Thank you!

There is a typo in the AIAction method:
this.oMovesPosition = pos; //the position on the board where the O would be placed
this.minimaxVal = 0; //the minimax value of the state that the action leads to
this.applyTo = function(state) {
var next = new State(state);
//if the current turn in the current state is O, increment .oMovesCount
next.board[this.movePosition] = state.turn;
if (state.turn === "o") {
next.oMovesCount++;
}
next.advanceTurn();
return next;
};
Notice the this.oMovesPosition in the first line, but then the applyTo method refers to this.movePosition instead.

Too recursion, in fiddle line 395 you call the function minimax in recursion.
var nextScore = miniMax(nextState);
You must stop recursion before run out of memory, or convert the recursion into a loop.

In AIaction = function(pos) you have two spellings for what should be the same movePosition property:
this.oMovesPosition = pos;
and:
next.board[this.movePosition] = state.turn;
Because they are different properties, the second line will always be equivalent to this:
next.board[undefined] = state.turn;
... and so the board never actually changes. As a consequence a board is never considered terminal, and your recursion never stops.

Related

Happy numbers - recursion

I have an issue with a recursive algorithm, that solves the problem of finding the happy numbers.
Here is the code:
function TestingFunction(number){
sumNumberContainer = new Array(0);
CheckIfNumberIsHappy(number);
}
function CheckIfNumberIsHappy(number){
var sumOfTheNumbers = 0;
for (var i = 0; i < number.length; i++) {
sumOfTheNumbers += Math.pow(parseInt(number[i]), 2);
}
console.log(sumOfTheNumbers);
if(sumOfTheNumbers == 1){
return CheckIfNumberIsHappy(sumOfTheNumbers.toString());
//return true;
} else {
sumNumberContainer.push(sumOfTheNumbers);
if(sumNumberContainer.length > 1){
for (var i = 0; i < sumNumberContainer.length - 1; i++) {
for (var j = i + 1; j < sumNumberContainer.length; j++) {
if(sumNumberContainer[i] == sumNumberContainer[j]){
return CheckIfNumberIsHappy(sumOfTheNumbers.toString());
//return false;
}
}
}
}
CheckIfNumberIsHappy(sumOfTheNumbers.toString());
}
}
Algorithm is working ALMOST fine. I've tested it out by calling function with different numbers, and console was displaying correct results. The problem is that I almost can't get any value from the function. There are only few cases in which I can get any value: If the number is build out of ,,0", and ,,1", for example 1000.
Because of that, I figured out, that I have problem with returning any value when the function is calling itself again.
Now I ended up with 2 results:
Returning the
return CheckIfNumberIsHappy(sumOfTheNumbers.toString());
which is giving an infinity looped number. For example when the number is happy, the function is printing in the console number one again and again...
Returning the
//return true
or
//return false
which gives me an undefined value
I'm a little bit in check by this problem, and I'm begging you guys for help.
I would take a step back and reexamine your problem with recursion in mind. The first thing you should think about with recursion is your edge cases — when can you just return a value without recursing. For happy numbers, that's the easy case where the sum of squares === 1 and the harder case where there's a cycle. So test for those and return appropriately. Only after that do you need to recurse. It can then be pretty simple:
function sumSq(num) {
/* simple helper for sums of squares */
return num.toString().split('').reduce((a, c) => c * c + a, 0)
}
function isHappy(n, seen = []) {
/* seen array keeps track of previous values so we can detect cycle */
let ss = sumSq(n)
// two edge cases -- just return
if (ss === 1) return true
if (seen.includes(ss)) return false
// not an edge case, save the value to seen, and recurse.
seen.push(ss)
return isHappy(ss, seen)
}
console.log(isHappy(23))
console.log(isHappy(22))
console.log(isHappy(7839))
Here's a simplified approach to the problem
const digits = x =>
x < 10
? [ x ]
: [ ...digits (x / 10 >> 0), x % 10 ]
const sumSquares = xs =>
xs.reduce ((acc, x) => acc + x * x, 0)
const isHappy = (x, seen = new Set) =>
x === 1
? true
: seen.has (x)
? false
: isHappy ( sumSquares (digits (x))
, seen.add (x)
)
for (let n = 1; n < 100; n = n + 1)
if (isHappy (n))
console.log ("happy", n)
// happy 1
// happy 7
// happy 10
// ...
// happy 97
The program above could be improved by using a technique called memoization
Your code is almost correct. You just forgot to return the result of the recursive call:
function TestingFunction(number){
sumNumberContainer = new Array(0);
if (CheckIfNumberIsHappy(number))
console.log(number);
}
function CheckIfNumberIsHappy(number){
var sumOfTheNumbers = 0;
for (var i = 0; i < number.length; i++) {
sumOfTheNumbers += Math.pow(parseInt(number[i]), 2);
}
if(sumOfTheNumbers == 1){
return true;
} else {
sumNumberContainer.push(sumOfTheNumbers);
if(sumNumberContainer.length > 1){
for (var i = 0; i < sumNumberContainer.length - 1; i++) {
for (var j = i + 1; j < sumNumberContainer.length; j++) {
if(sumNumberContainer[i] == sumNumberContainer[j]){
return false;
}
}
}
}
return CheckIfNumberIsHappy(sumOfTheNumbers.toString());
}
}
for (let i=0; i<100; ++i)
TestingFunction(i.toString()); // 1 7 10 13 ... 91 94 97
I've got the solution, which was given to me in the comments, by the user: Mark_M.
I just had to use my previous
return true / return false
also I had to return the recursive statement in the function, and return the value of the CheckIfTheNumberIsHappy function, which was called in TestingFunction.
The working code:
function TestingFunction(number){
sumNumberContainer = new Array(0);
return CheckIfNumberIsHappy(number);
}
function CheckIfNumberIsHappy(number){
var sumOfTheNumbers = 0;
for (var i = 0; i < number.length; i++) {
sumOfTheNumbers += Math.pow(parseInt(number[i]), 2);
}
console.log(sumOfTheNumbers);
if(sumOfTheNumbers == 1){
return true;
} else {
sumNumberContainer.push(sumOfTheNumbers);
if(sumNumberContainer.length > 1){
for (var i = 0; i < sumNumberContainer.length - 1; i++) {
for (var j = i + 1; j < sumNumberContainer.length; j++) {
if(sumNumberContainer[i] == sumNumberContainer[j]){
return false;
}
}
}
}
return CheckIfNumberIsHappy(sumOfTheNumbers.toString());
}
}
Thanks for the great support :)

Javascript while loop (Card deck simulation)

I am having an issue with the following code that simulates a card deck.
The deck is created properly (1 array containing 4 arrays (suits) containing 13 elements each (face values)) and when I use the G.test(); function it is correctly pulling 13 random cards but then returns 39x "Empty" (A total of 52).
I hate to ask for help, but I have left the problem overnight and then some and I still cannot find the reason that this is happening. I appreciate any and all insight that can be offered.
var G = {};
G.cards = [[], [], [], []];
G.newCard = function(v) { //currently a useless function, tried a few things
return v;
};
G.deck = {
n: function() { //new deck
var x; var list = [];
list.push(G.newCard("A"));
for (x = 2; x <= 10; x += 1) {
list.push(G.newCard(x.toString()));
}
list.push(G.newCard("J"), G.newCard("Q"), G.newCard("K"));
for (x = 0; x < G.cards.length; x += 1) {
G.cards[x] = list;
}
},
d: function() { //random card - returns suit & value
var s; var c; var v; var drawn = false; var n;
s = random(0, G.cards.length);
c = random(0, G.cards[s].length);
n = 0;
while (!drawn) {
if (G.cards[s].length > 0) {
if (G.cards[s][c]) {
v = G.cards[s].splice(c, 1);
drawn = true;
} else {
c = random(0, G.cards[s].length);
}
} else {
s = (s + 1 >= G.cards.length) ? 0 : s + 1;
n += 1;
console.log(s);
if (n >= G.cards.length) {
console.log(n);
return "Empty";
}
}
}
return {s: s, v: v[0]};
},
}; //G.deck
G.test = function() {
var x; var v;
G.deck.n();
for (x = 0; x < 52; x += 1) {
v = G.deck.d();
console.log(v);
}
};
Replace
for (x = 0; x < G.cards.length; x += 1) {
G.cards[x] = list;
}
with
for (x = 0; x < G.cards.length; x += 1) {
G.cards[x] = list.slice();
}
as this prevents all elements of G.cards[x] binding to the same (single) array instance.
If all elements bind to the same instance, mutating one element equals mutating all elements. list.slice() creates a new copy of list and thus a new array instance to prevent the aforementioned issue.
I won't go through your code, but I built a code that will do what you wanted. I only built this for one deck and not multiple deck play. There are two functions, one will generate the deck, and the other will drawn cards from the deck, bases on how many hands you need and how many cards you wanted for each hand. One a card is drawn, it will not be re-drawn. I might publish a short article for how a card dealing program work or similar in the short future at http://kevinhng86.iblog.website.
function random(min, max){
return Math.floor(Math.random() * (max - min)) + min;
}
function deckGenerate(){
var output = [];
var face = {1: "A", 11: "J", 12: "Q", 13: "K"};
// Heart Space Diamond & Club;
var suit = ["H", "S", "D", "C"];
// Delimiter between card identification and suit identification.
var d = "-";
for(i = 0; i < 4; i++){
output[i] = [];
for(ind = 0; ind < 13; ind++ ){
card = (ind + 1);
output[i][ind] = (card > 10) || (card === 1)? face[card] + d + suit[i] : card.toString() + d + suit[i];
}
}
return output;
}
function randomCard(deck, hand, card){
var output = [];
var randS = 0;
var randC = 0;
if( hand * card > 52 ) throw("Too many card, I built this for one deck only");
for(i = 0; i < hand; i++){
output[i] = [];
for(ind = 0; ind < card; ind++){
randS = random(0, deck.length);
randC = random(0, deck[randS].length);
output[i][ind] = deck[randS][randC];
deck[randS].splice(randC,1);
if(deck[randS].length === 0) deck.splice(randS,1);
}
}
document.write( JSON.stringify(deck, null, 2) );
return output;
}
var deck = deckGenerate()
document.write( JSON.stringify(deck, null, 2) );
document.write("<br><br>");
var randomhands = randomCard(deck, 5, 8);
document.write("<br><br>");
document.write("<br><br>");
document.write( JSON.stringify(randomhands, null, 2) );

- I am trying to write a code where input should be abbbcc and output should be a1b3c2

I am new to js.
I am trying to write a code where input should be abbbcc and output should be a1b3c2.
not sure how to get it
providing code below
var word = "abbbcc";
var countword = [];
for (i=0; i < word.length; i++) {
if (word[i] === charAt && word[i] != word[i+1]) {
countword.push(word[i]);
countword.push(i++);
}
else (word[i] === charAt && word[i] != word[i+1]) {
countword.push(word[i]);
for (i=0; i < word.length; i++) {
if (word[i+1] === word[i+2]) {
countword.push(i++);
}
else{
break;
}
}
}
}
console.log("result----->" + countword);
It can be done using a for loop and a counter like this.
var word = "abbbcc";
var countword = "";
var counter = 1;
for (i=0; i < word.length; i++) {
if ( word[i] != word[i+1]) {
// Save the letter and the counter
countword += word[i]+counter;
// Reset the counter
counter=1;
}else{
// Increment counter
counter++;
}
}
console.log("result-----> " + countword );
Alternative solution using Array#reduce. I've described each step, I hope you will get my point and understand how does it works.
var word = "abbbcc".split(''),
res = '',
counter = 1;
word.reduce(function(s, a, i, r) {
if (s !== a) { //if the succeeding element doesn't match the previous one
res += s + counter; //concat it to the string + the amount of appearances (counter)
counter = 1; //reset the counter
} else {
counter++; //the succeeding element matches the previous one, increment the counter
}
if (i === r.length - 1 && counter > 0) { //if the loop is over
res += s + counter; //add the last element
}
return a;
})
console.log(res);

Encryption... almost works

I wrote a simple script for a website called Codewars (here: https://www.codewars.com/kata/57814d79a56c88e3e0000786). The purpose of the function was to encrypt a string such that every second character would appear first, and then the rest of them. I tested many random strings of text; it worked for a while. But then, I tested a specific case with 17 characters: "maybe do i really", and it resulted in a character being dropped (notably a space). Initially, I thought the issue was that the .join method didn't allow a double space in a row, so I attempted to make my own function to mimic its functionality: it did not solve the problem. Could anyone answer why this specific string loses a character and returns a wrong encryption? My jsfiddle is here: https://jsfiddle.net/MCBlastoise/fwz62j2g/
Edit: I neglected to mention that it runs a certain number of times based on parameter n, encrypting the string multiple times per that value.
And my code is here:
function encrypt(text, n) {
if (n <= 0 || isNaN(n) === true || text === "" || text === null) {
return text;
}
else {
for (i = 1; i <= n; i++) {
if (i > 1) {
text = encryptedString;
}
var evenChars = [];
var oddChars = [];
for (j = 0; j < text.length; j++) {
if (j % 2 === 0) {
evenChars.push(text.charAt(j));
}
else {
oddChars.push(text.charAt(j));
}
}
var encryptedString = oddChars.join("") + evenChars.join("");
}
return encryptedString;
}
}
function decrypt(encryptedText, n) {
if (n <= 0 || encryptedText === "" || encryptedText === null) {
return encryptedText;
}
else {
for (i = 1; i <= n; i++) {
if (i > 1) {
encryptedText = decryptedString;
}
var oddChars = [];
var evenChars = [];
for (j = 0; j < encryptedText.length; j++) {
if (j < Math.floor(encryptedText.length / 2)) {
oddChars.push(encryptedText.charAt(j));
}
else {
evenChars.push(encryptedText.charAt(j));
}
}
var convertedChars = []
for (k = 0; k < evenChars.length; k++) {
convertedChars.push(evenChars[k]);
convertedChars.push(oddChars[k]);
}
var decryptedString = convertedChars.join("");
}
return decryptedString;
}
}
document.getElementById("text").innerHTML = encrypt("maybe do i really", 1);
document.getElementById("text2").innerHTML = decrypt("ab oiralmyed ely", 1)
<p id="text"></p>
<p id="text2"></p>
Nothing wrong with the code itself. Basically HTML doesn't allow 2 or more spaces. You can use <pre> tag for the case like this.
document.getElementById("text").innerHTML = "<pre>" + encrypt("maybe do i really", 1) + "</pre>";

Battleship game: Allowing hits to be registered on ships with lengths other than 4

I'm making Battleship in JavaScript for a school project and I'm stuck.
The computer randomly generates boats and the user has to try to sink all the computer's boats by entering in coordinates. The hits on a boat are saved in an array. The array contains zeros to signify the boat's length (so boat with length 4 has an array with 4 zeros). When a boat is hit, the 0 changes to 1. If all the elements are 1, the boat sinks. Problem is my code registers the hits, but only puts it in an array when you hit the boat with length 4.
Can anyone help me? Below is the code of the "game" itself:
function game() {
inputArray = [4, 3, 2];
var boats = randomBoats(inputArray);
var currentBoat = 0;
var sunkenBoat = 0;
var numberOfTurns = 0;
while (sunkenBoat !== inputArray.length) {
var hit = false;
var target = "(" + prompt("Enter targetcoordinate (x,y)") + ")";
var targetString = target.replace(/\s+/g, '');
for (var i = 0; i !== inputArray.length; i++) {
for (var j = 0; j !== boats[i].usedPositions().length; j++) {
console.log(targetString)
if (targetString === boats[i].usedPositions()[j].toString()) {
raak = true;
boats[i].hits[j] = 1;
console.log(boats[i].hits);
currentBoat = boats[i];
} else {
currentBoat = boats[i];
}
}
}
console.log(currentBoat.hits);
console.log(allEquals(currentBoat.hits, 1));
if (hit)
alert("Hit!");
else
alert("Miss!");
if (allEquals(currentBoat.hits, 1)) {
alert("Boat with length " + currentBoat.hits.length + " has sunken!");
sunkenBoat++;
}
numberOfTurns++
}
alert("You've won! You did it in " + numberOfTurns + " turns.")
}
The issue is with the else in the inner loop, as you processed each ship (outer loop) and all positions (inner loop) your
else {
currentBoat = boats[i];
}
would always set the current boat to be the last [i];
You also don't need to process any other positions or boats when you've found a hit, so break early when you detect the hit like this:
raak = false;
for(var i = 0; i < inputArray.length && !raak; i++) {
for(var j = 0; j !== boats[i].usedPositions().length; j++) {
console.log(targetString)
if(targetString === boats[i].usedPositions()[j].toString()) {
raak = true;
boats[i].hits[j] = 1;
console.log(boats[i].hits);
currentBoat = boats[i];
break;
}
}
}

Categories

Resources