JS Class property "is undefined" even tho it appears to be defined - javascript

I'm quite new to JS and I'm trying to do a TicTacToe game with a custom size board in an attempt to learn a bit more. I first coded just a 3x3 version and started building up from there.
Right as I got past the point where I have a custom grid size entered right after loading the page and the grid rendering, I started getting the same problem when trying to click any cell to try and play a turn.
"this.game_state[clicked_cell_i] is undefined".
I have tried opening up F12 and checking if the game_state array (which is a 2d array of strings that tracks which cell is played and which isn't) but when I do everything seems normal and the array gets printed out without problem. (picture showcases printing out the game_state array in a 4x4 grid) https://i.stack.imgur.com/gV0pY.png
I would really appreciate it if somebody could explain to me what's happening or even better - help me fix it. Thanks :)
Code: https://jsfiddle.net/z8649pxL
class game {
status_display;
is_game_active;
curr_player;
game_state;
constructor() {
this.status_display = document.querySelector('.status');
this.is_game_active = true;
this.curr_player = "X";
this.game_state = matrix(rows, rows, "");
}
cell_played(clicked_cell, clicked_cell_i, clicked_cell_j){
this.game_state[clicked_cell_i][clicked_cell_j] = this.curr_player;
clicked_cell.innerHTML = this.curr_player;
if(this.curr_player === "X"){
document.getElementById((i*rows)+j).style.backgroundColor = "#ff6600";
} else if(this.curr_player === "O"){
document.getElementById((i*rows)+j).style.backgroundColor = "#33ccff";
}
}
cell_click(clicked_cellEvent){
debugger
const clicked_cell = clicked_cellEvent.target;
let clicked_cell_i = parseInt(clicked_cell.getAttribute('i'));
let clicked_cell_j = parseInt(clicked_cell.getAttribute('j'));
if(this.game_state[clicked_cell_i][clicked_cell_j] !== "" || !this.is_game_active) {
return;
}
this.cell_played(clicked_cell, clicked_cell_i, clicked_cell_j);
this.res_validation();
}
};
let ex_game = new game();
function create_grid() {
document.getElementById('hidestart').style.display = "none";
var Container = document.getElementsByClassName("grid");
Container.innerHTML = '';
rows = prompt("n?");
let i = 0, j = 0;
document.documentElement.style.setProperty("--columns-row", rows);
for (i = 0; i < rows ; i++) {
for(j = 0; j < rows; j++){
var div = document.createElement("div");
div.className = "cell";
div.id = (i*rows)+j;
div.setAttribute("cell-index", (i*rows)+j);
div.setAttribute("i", i);
div.setAttribute("j", j);
let wrapper = document.getElementsByClassName("grid");
wrapper[0].appendChild(div);
}
}
document.querySelectorAll('.cell').forEach(cell => cell.addEventListener('click', (e) => {
ex_game.cell_click(e);
e.stopPropagation();
}));
document.getElementById('hidestart').style.display = "block";
}
function matrix(rows, cols, defaultValue){
var arr = [];
// Creates all lines:
for(var i=0; i < rows; i++){
// Creates an empty line
arr.push([]);
// Adds cols to the empty line:
arr[i].push(new Array(cols));
for(var j=0; j < cols; j++){
// Initializes:
arr[i][j] = defaultValue;
}
}
return arr;
}```

When you call
new game()
the constructor is been called of that class.
And in that constructor you are calling matrix which requires the value of rows.
But initially the class do not have any value.
So the state is not getting initialized at that point of time.
That why when you use the array, it is getting undefined.
So to resolve this issue, you could just try to get the object of class game after getting the rows from user, and pass that rows in the arguments when calling the constructor of the class.
let ex_game = new game(rows);
And then using this argument call the function matrix.
Edit:
I have looked into your code.
The error is in the method called cell_played. you are using i and j which are not known to it. Please replace your code with following line. This would resolve your error.
cell_played(clicked_cell, clicked_cell_i, clicked_cell_j){
this.game_state[clicked_cell_i][clicked_cell_j] = this.curr_player;
clicked_cell.innerHTML = this.curr_player;
if(this.curr_player === "X"){
document.getElementById((clicked_cell_i*rows)+clicked_cell_j).style.backgroundColor = "#ff6600";
} else if(this.curr_player === "O"){
document.getElementById((clicked_cell_i*rows)+clicked_cell_j).style.backgroundColor = "#33ccff";
}
}
And also remove the parameter rows from constructor and just call the constructor after taking the number of rows from user.
let ex_game
function create_grid() {
/* document.getElementById('hidestart').style.display = "none" */
var Container = document.getElementsByClassName("grid");
Container.innerHTML = '';
rows = prompt("n?");
let i = 0, j = 0;
ex_game= new game().........

Related

I can't make setTimeout() work in my script

I'm making a memory game. It already sort of works, but when I choose two cards that aren't the same picture, they flip back instantly, without showing the user what is on the second card. I think I should use setTimeout(), but for now it only made my flipBack() function not work at all. Maybe, I am using it incorrectly or in the wrong place.
I tried putting both the flipBack() and setTimeout(flipBack, 5000) inside "else if", outside of it, outside of my revealCard() function.
I'm only getting "Uncaught TypeError: Cannot read properties of undefined (reading 'style')
at flipBack (memory game.js:61:35)" in the console. It seems that setTimeout cannot execute flipBack() or something like that.
let board = document.getElementById("board");
let score = document.getElementById("score");
let fails = document.getElementById("fails");
let cards = document.querySelectorAll(".card");
// add eventListener to every card
for (let i = 0; i < 6; i++) {
cards[i].addEventListener("click", revealCard);
}
let revealCounter = 2;
let imgAltArray = [];
let latestTwoRevealedCards = [];
let points = 0;
let wrongGuesses = 0;
function revealCard(event) {
if (revealCounter > 0) {
let cardImg = event.target.firstChild;
// make card "flip", so you can see the picture
cardImg.style.visibility = "visible";
imgAltArray.push(cardImg.alt);
latestTwoRevealedCards.push(cardImg);
console.log(imgAltArray);
revealCounter--;
// check if both cards have the same picture on them by comparing alt parameters
if (revealCounter == 0 && imgAltArray[0] === imgAltArray[1]) {
imgAltArray = [];
latestTwoRevealedCards = [];
points++
score.textContent = `Score: ${points}`;
revealCounter = 2;
}
else if (revealCounter == 0 && imgAltArray[0] !== imgAltArray[1]) {
wrongGuesses++;
fails.textContent = `Failed attempts: ${wrongGuesses}`
imgAltArray = [];
// make cards flip back
setTimeout(flipBack, 5000);
latestTwoRevealedCards = [];
revealCounter = 2;
}
}
}
// TIMEOUT DOESN"T WORK
function flipBack() {
for (let i = 1; i >= 0; i--) {
latestTwoRevealedCards[i].style.visibility = "hidden";
}
}
latestTwoRevealedCards[i] This element may be undefined

Array not being updated after switching indexs

I'm working on a visualizer for sorting algorithms. Everything is working as intended until I got to the Selection Sort. I understand that the Selection sort will make a pass and search for the MINIMUM value and then swap that the index that it started at in the array. However, each time it makes a pass, the i value doesn't change. I tested it by changing the color of the block the i index represents in my loop and it never changes, so the MINIMUM value just keeps switching to where ever the i is.
You can view my project here on GitHub Pages, just use the left Navbar to choose Selection Sort and you can see the problem I'm having. The bottom snippet is my swap function, it didn't do this with any of the other sort methods, only the selection sort.
Github Pages -- https://kevin6767.github.io/sorting-algorithm-visualization/
Selection function
async function selectionSort() {
let blocks = document.querySelectorAll('.block');
for (let i = 0; i < blocks.length; i++) {
// Assume a minimum value
let min = i;
for (let j = i + 1; j < blocks.length; j++) {
blocks[j].style.backgroundColor = '#FF4949';
blocks[min].style.backgroundColor = '#13CE66';
blocks[i].style.backgroundColor = 'orange';
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, frame_speed)
);
const value1 = Number(blocks[j].childNodes[0].innerHTML);
const value2 = Number(blocks[min].childNodes[0].innerHTML);
if (value1 < value2) {
blocks[min].style.backgroundColor = '#58B7FF';
min = j;
}
blocks[j].style.backgroundColor = '#58B7FF';
}
if (min !== i) {
let tmp = blocks[i];
blocks[i] = blocks[min];
blocks[min] = tmp;
await swap(blocks[i], blocks[min]);
blocks = document.querySelectorAll('.block');
}
// Swap if new minimun value found
blocks[i].style.backgroundColor = '#58B7FF';
}
}
Swap function
function swap(el1, el2) {
return new Promise((resolve) => {
const style1 = window.getComputedStyle(el1);
const style2 = window.getComputedStyle(el2);
const transform1 = style1.getPropertyValue('transform');
const transform2 = style2.getPropertyValue('transform');
el1.style.transform = transform2;
el2.style.transform = transform1;
// Wait for the transition to end!
window.requestAnimationFrame(function () {
setTimeout(() => {
container.insertBefore(el2, el1);
resolve();
}, 300);
});
});
}
I ended up fixing it. It seems that I had to take the Nodelist array I was getting from just .querySelectorAll and convert that into an array using .Arrayfrom() which was pretty simple after some googling. From then on I needed to figure out how to update the array each pass, which once again was as simple as just moving one index from another.
The interesting part of the answer was how I was going to update the Nodelist itself that way all my css code would still work (This is a sorting visualizer, so it would show you what element it was on and highlight it with a color). The answer however was right in front of me. Even though I turned the Nodelist array into a regular array, I was still able to apply styles to it. This meant I didn't have to mutate the Nodelist array at all and was just able to keep a seperate array within the function to work with.
PS. The algorithm did have a lot of trouble in the above snippet because I was comparing 2 strings in the if statement (value1 and value2) this is what caused a lot of the actual algorithm erroring and was simply fixed by adding a Number() function around my innerhtml code.
Selection
async function selectionSort() {
let blocks = document.querySelectorAll('.block');
let convertedBlocks = Array.from(blocks);
let len = convertedBlocks.length;
for (let i = 0; i < len; i++) {
let min = i;
for (let j = i + 1; j < len; j++) {
convertedBlocks[j].style.backgroundColor = 'red';
convertedBlocks[min].style.backgroundColor = 'green';
convertedBlocks[i].style.backgroundColor = 'orange';
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, frame_speed)
);
if (
Number(convertedBlocks[min].childNodes[0].innerHTML) >
Number(convertedBlocks[j].childNodes[0].innerHTML)
) {
convertedBlocks[min].style.backgroundColor = '#58B7FF';
min = j;
}
convertedBlocks[j].style.backgroundColor = '#58B7FF';
}
if (min !== i) {
let tmp = convertedBlocks[i];
convertedBlocks[i] = convertedBlocks[min];
convertedBlocks[min] = tmp;
await swap(convertedBlocks[i], convertedBlocks[min]);
}
convertedBlocks[min].style.backgroundColor = '#58B7FF';
convertedBlocks[i].style.backgroundColor = '#58B7FF';
}
}

Why does one function works, but the other does not?

This one works, it shows the table:
When I put .style.display = "tablerow", the function works
var getMain = document.getElementById("main").style.display = "table-row";
var getLocalStorage = localStorage.hasOwnProperty('tableFill');
for (i = 0; i < getLocalStorage.lenght; i++) {
if (getLocalStorage[i]){
getMain;
But when I put it in in the for loop it doesn't work anymore, but I also get no error at all in the console...
var getMain = document.getElementById("main");
var getLocalStorage = localStorage.hasOwnProperty('tableFill');
for (i = 0; i < getLocalStorage.lenght; i++) {
if (getLocalStorage[i]){
getMain.style.display = "table-row";
localStorage.hasOwnProperty('tableFill') returns true or false. Does not have any length so you won't get into the loop.
localStorage.hasOwnProperty('tableFill') returns a boolean to check if the property exists or not. It does not have data to make a loop out of it

Uncaught TypeError: Cannot read property '0' of undefined in JavaScript Memory game

I have a problem with my JavaScript memory game, it's supposed to create some <div/> which will be responsible as a our memory game cards and the user can input before pressing the start button how much rows and columns he wants to have. But somehow I got an error saying: Uncaught TypeError: Cannot read property '0' of undefined. This is the code responsible for creating the div table and not only:
function GameBoard(n,m) {
this.n = n;
this.m = m;
score = 0;
//n*m%2==0
picBoard = new Array(n);
last = -1;
accepTab = new Array(n*m/2);
for (var i=0; i<accepTab.length; i++){
accepTab[i] = 0;
}
for (var i=0; i<picBoard.length; i++){
picBoard[i] = new Array(m);
for (var j=0; j<picBoard[i].length; j++){
picBoard[i][j] = new Picture(accepTab);
}
}
function getPicture(id) {
console.log(picBoard);
return picBoard[id[0]][id[1]].val
}
function show(obj){
val = getPicture(getId(obj.id));
$(obj).html(val);
if(last==-1){
last = val;
lastId = getId(obj.id);
} else {
if(last==val){
$('#score').html(++score);
} else {
setTimeout(function(){
$(obj).html("X");
$("#col"+lastId[0]+"_"+lastId[1]).html("X");
}, 1000);
}
last=-1;
}
}
for(var i=0; i<n; i++){
$("#board").append("<div class=row id=row"+i+"> ");
for(var j=0; j<m; j++){
$("#board #row"+i).append("<span class=col id=col"+i+"_"+j+">X");
$("#col"+i+"_"+j).bind("click", function() {
show(this);
});
}
}
}
and the Console is saying the problem is with the line of function getPicture with return picBoard[id[0]][id[1]].val. And the function for Starting the game
var board;
function start(){
var n = document.getElementById("rows").value;
var m = document.getElementById("cols").value;
board = new GameBoard(n,m);
sec=0;
$("#start").unbind("click");
}
Whenever I call the function GameBoard without n and m variables but instead of values like 3 and 4 everything is working fine, it's just the thing when I tried to give a user an option to choose how big he wants to have the table of memory cards.
n and m are strings, not numbers. So some of the operations you do will result in different things. So convert them to numbers
board = new GameBoard(+n,+m);

JavaScript Variable in method modifying object's attribute?

I'm trying to create a poker game in JavaScript. I thought the function that tested flushes worked perfectly until I displayed the hand AFTER the method ran. If there is a flush, this function is supposed to show the user's full hand, and then only the flush underneath it. Instead, it shows only the flush, and then the flush again underneath it:
var $ = function (id) { return document.getElementById(id); };
var test = function() {
var deck = new POKER.Deck(); //creates deck
var hand = new POKER.Hand(); //creates hand
//----------------TEST HAND UNTIL FLUSH-----------------
while (hand.getValue() != POKER.HAND_TYPE.FLUSH) {
deck.refreshDeck(); //refresh deck with new cards
for (var i = 0; i < 7; ++i) { //populate hand
hand.addCard(deck.dealCard());
}
console.log(hand.size() + " before"); //only for debugging. Prints "7 before"
hand.testFlush();
console.log(hand.size() + " after"); //only for debugging. Result unexpected
if (hand.getValue() == POKER.HAND_TYPE.FLUSH) { //if hand has a flush
for (var j = 0; j < hand.size(); j++) { //display full hand
var img = document.createElement("img");
var card = hand.getCardAtIndex(j);
img.src = card.getImage();
$("images").appendChild(img);
}
for (var k = 0; k < 5; k++) { //display flush hand
var img2 = document.createElement("img");
var card2 = hand.getValueCardAtIndex(k);
img2.src = card2.getImage();
$("handImg").appendChild(img2);
}
break;
} else {
hand.empty();
}
}
};
window.onload = function() {
test();
};
The second console.log statement prints out "4 after" until the testFlush method detects a flush, and the final result is "5 after".
testFlush method:
POKER.Hand.prototype.testFlush = function() {
//first, sort cards by rank so that the highest flush is
//taken if there are more than five cards of the same suit
this.sortByRank();
this.sortBySuit();
var tempHand = this.cards; //modifiable version of this.cards
var NUM_OF_TESTS = 3; //only 3 loops required to test for all possible flushes
var LAST_CARD_INDEX = 4; //represents the fifth card, or index 4
var MAX_CARDS = 5; //maximum cards possible in a hand (valueCards)
for (var i = 1; i <= NUM_OF_TESTS; i++){
//check if 1st and 5th cards are the same suit
if(tempHand[0].getSuit() == tempHand[LAST_CARD_INDEX].getSuit()){
this.value = POKER.HAND_TYPE.FLUSH;
while(tempHand.length != MAX_CARDS){ //remove last card in tempHand until there are only five cards
tempHand.pop();
}
this.valueCards = tempHand;
}else{
tempHand.splice(0,1); //removes first card from the temporary hand
}
}
};
All "hand.size()" does in the test function is "return this.cards.length". So what I don't understand is how the testFlush method could be altering the object attribute "this.cards" when it only alters the temporary variable tempHand.
Hand object:
POKER.Hand = function(){
this.cards = [];
this.value; //integer that corresponds to the POKER.HAND_TYPE
this.valueCards = []; //array of the five cards that corresponds only to this.value
};
Hand.size method:
POKER.Hand.prototype.size = function() {
return this.cards.length;
};
The problem is this line:
var tempHand = this.cards; //modifiable version of this.cards
Assigning an array or object to a variable does not make a copy of it. The variable is a reference to the same array, so tempHand.pop() modifies this.cards as well. You can make a copy of an array with .slice():
var tempHand = this.cards.slice();

Categories

Resources