showMoves is a function made to show flashing for a simon game.
When the flashing lights are over I clear the interval to stop it and then I set game.playerTurn to true so I can click on colors, but game.playerTurn is changing to true as soon as showMoves is activated.
I want game.playerTurn to stay false until the showMoves function is finished showing flashing.
Here are the functions I'm using game.playerTurn in -
game.playerTurn = false;
//for flashing lights
function showMoves() {
let i = 0;
const start = setInterval(function () {
if (i >= game.computerMoves.length) {
clearInterval(start);
game.playerTurn = true;
return;
}
const move = game.computerMoves[i];
setLight(move, true);
setTimeout(setLight.bind(null, move, false), 1000); //Using bind to preset arguments
i++;
}, 2000);
}
function setLight(color, isOn) {
if (isOn) {
sounds[color.id].play();
}
color.style.backgroundColor = isOn ? colors[0].get(color) : colors[1].get(color);
}
//compareMoves is fired everytime I click on a color
function compareMoves(e) {
if (e === game.computerMoves[game.counter]) {
game.counter++;
//This is if all the moves were chosen correctly
if (game.playerMoves.length === game.computerMoves.length && e === game.computerMoves[game.computerMoves.length - 1]) {
simonHTML.displayScore.textContent = ++game.score;
game.playerTurn = false;
resetMoves();
randomMoves(++game.turn);
showMoves();
game.counter = 0;
}
} else if (game.strict) {
//if your move was wrong do this
} else {
game.playerMoves = [];
game.counter = 0;
game.playerTurn = false;
showMoves();
return false;
}
}
I'd appreciate any help with this. Here is a link to the game and all the code https://codepen.io/icewizard/pen/JLBpNQ
Where are you setting game.playerTurn back to false?
function showMoves() {
game.playerTurn = false;
let i = 0;
const start = setInterval(function() {
if (i >= game.computerMoves.length) {
clearInterval(start);
game.playerTurn = true;
return;
}
const move = game.computerMoves[i];
setLight(move, true);
setTimeout(setLight.bind(null, move, false), 1000); //Using bind to preset arguments
i++;
}, 2000);
}
Seems to work for me in the codepen example you provided
Related
I'll show you the code but let me explain.
If i click on an arrow (see screen 1), the updateArrows function is called and at the end, next() is called for update the steps.
Everything is working perfectly, if I click on the down arrow until step 4, no problem. On the screen 1 you can see the result of the console.log(nextPage) which is in the next() function.
After that, if i click on the top arrow 2 times (if i go back to the step 2 then), at the step 3, its all good, at the step 2, the currentPage.addEventListener("animationend",function (e) { }) is executed twice, on the console you can see the 2 last nextPage values 2 times, one time with the good value (2) and one time with the value 4 (see screen 2).
I think the problem come from a double listening of the animationend event, but at first I can't explain why the problem does not occur before ?
Screen 1 :
Screen 2 :
Code :
const changeStep = document.querySelectorAll(".step");
const currentPaginate = document.querySelector(".pagination span.active");
const arrows = document.querySelectorAll(".arrow");
let isAnimating = false;
for (let arrow of arrows) arrow.addEventListener("click", updateArrows);
for (let step of changeStep) step.addEventListener("click", next);
function updateArrows(e, currentStep = null) {
if (isAnimating) return false;
let arrow = null;
e.target ? (arrow = e.target) : (arrow = e);
let nextStep;
if (currentStep == null) {
currentStep = document.querySelector(".step.current");
if (arrow.classList.contains("arrow-bottom")) {
nextStep = currentStep.nextElementSibling;
} else {
nextStep = currentStep.previousElementSibling;
}
} else {
nextStep = document.querySelector(".step.current");
}
if (!arrow.classList.contains("impossible")) {
if (nextStep.dataset.id != 1 && nextStep.dataset.id != 4) {
arrows.forEach(function (arrow) {
if (arrow.classList.contains("impossible")) {
arrow.classList.remove("impossible");
}
});
} else if (nextStep.dataset.id == 4) {
if (arrow.previousElementSibling.classList.contains("impossible"))
arrow.previousElementSibling.classList.remove("impossible");
arrow.classList.add("impossible");
} else if (nextStep.dataset.id == 1) {
if (arrow.nextElementSibling.classList.contains("impossible"))
arrow.nextElementSibling.classList.remove("impossible");
arrow.classList.add("impossible");
}
if (e.target != undefined) next(nextStep);
}
}
function next(e) {
if (isAnimating) {
return false;
}
isAnimating = true;
let step = null;
const currentPage = document.querySelector(".page.current");
e.target ? (step = e.target) : (step = e);
if (!step.classList.contains("current")) {
const currentStep = document.querySelector(".step.current");
const nextStep = step.dataset.id;
const nextPage = document.querySelector('[data-id="' + nextStep + '"]');
currentStep.classList.remove("current");
step.classList.add("current");
currentPaginate.textContent = "0" + nextStep;
let arrow;
if (currentStep.dataset.id < nextStep) {
arrow = document.querySelector(".arrow-bottom");
} else {
arrow = document.querySelector(".arrow-top");
}
currentPage.addEventListener(
"animationend",
function (e) {
e = window.event || e;
if (this === e.target) {
currentPage.classList.remove("current", "moveToBottomFade");
nextPage.classList.add("current");
console.log(nextPage);
isAnimating = false;
}
},
true
);
currentPage.classList.add("moveToBottomFade");
}
}
Problem solved... But if someone can explain me why, it would be perfect.
It seems like my removeEventlistener doesn't work because i just added a once:true to my listener, and now it works perfectly !
Code :
currentPage.addEventListener(
"animationend",
function (e) {
//if(isAnimating === true)
e = window.event || e;
if (this === e.target) {
currentPage.removeEventListener("animationend", e);
currentPage.classList.remove(
"current",
"moveToBottomFade"
);
nextPage.classList.add("current");
isAnimating = false;
}
},
{ once: true }
);
currentPage.classList.add("moveToBottomFade");
I'm creating a memory card game and it works until I try to click on the cards too fast. When I open two cards, I am calling compareCards function which adds document.body.style.pointerEvents = "none"; but obviously I can click on the third card if I am fast enough. How can I fix it? Here is my full JS code, note that class .flip adds pointer-events: none; among other things while .match adds short animation. I guess it probably has something to do with setTimeouts but I need them in order to show animatioins.
const playBtn = document.querySelector(".intro button");
const restartBtn = document.querySelectorAll(".restartBtn");
const introScreen = document.querySelector(".intro");
const game = document.querySelector(".game");
const gameContainer = document.querySelector("#gameContainer");
const timer = document.querySelector(".timer span");
const moves = document.querySelector(".moves span");
let time,
minutes = 0,
seconds = 0;
let numberOfMoves = 0;
moves.innerHTML = numberOfMoves;
let openCards = [];
let matchedCards = [];
function startGame() {
let shuffledDeck = shuffle(deckCards);
for (let i = 0; i < shuffledDeck.length; i++) {
const card = document.createElement("div");
card.classList.add("card");
const image = document.createElement("img");
image.setAttribute("src", "img/" + shuffledDeck[i]);
card.appendChild(image);
gameContainer.appendChild(card);
}
runTimer();
}
const deckCards = [
... images to add to game ...];
gameContainer.addEventListener("click", function (e) {
if (e.target.className === "card") {
flipCard();
}
function flipCard() {
e.target.classList.add("flip");
addCard();
}
function addCard() {
if (openCards.length == 0 || openCards.length == 1) {
openCards.push(e.target.firstElementChild);
}
compareCards();
}
});
function compareCards() {
if (openCards.length == 2) {
document.body.style.pointerEvents = "none";
}
if (openCards[0].src == openCards[1].src && openCards.length == 2) {
cardsMatched();
} else if (openCards[0].src !== openCards[1].src && openCards.length == 2) {
cardsNotMatched();
}
}
function countMoves() {
numberOfMoves++;
moves.innerHTML = numberOfMoves;
}
function cardsMatched() {
setTimeout(function () {
openCards[0].parentElement.classList.add("match");
openCards[1].parentElement.classList.add("match");
matchedCards.push(...openCards);
document.body.style.pointerEvents = "auto";
gameWon();
openCards = [];
}, 500);
countMoves();
}
function cardsNotMatched() {
setTimeout(function () {
openCards[0].parentElement.classList.remove("flip");
openCards[1].parentElement.classList.remove("flip");
document.body.style.pointerEvents = "auto";
openCards = [];
}, 500);
countMoves();
}
function gameWon() {
if (matchedCards.length == 16) {
stopTimer();
showModal();
}
}
const modal = document.querySelector(".modal");
function showModal() {
const closeModal = document.querySelector(".closeBtn");
modal.style.display = "block";
closeModal.addEventListener("click", () => {
modal.style.display = "none";
});
window.onclick = function (event) {
if (event.target == modal) {
modal.style.display = "none";
}
};
}
function resetEverything() {
modal.style.display = "none";
stopTimer();
timer.innerHTML = `00:00`;
numberOfMoves = 0;
moves.innerHTML = numberOfMoves;
matchedCards = [];
openCards = [];
startGame();
}
function shuffle(array) {
let currentIndex = array.length,
randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [
array[randomIndex],
array[currentIndex],
];
}
return array;
}
function runTimer() {
time = setInterval(() => {
seconds++;
if (seconds == 60) {
minutes++;
seconds = 0;
}
timer.innerHTML = `${minutes < 10 ? `0${minutes}` : minutes}:${
seconds < 10 ? `0${seconds}` : seconds
}`;
}, 1000);
}
function stopTimer() {
seconds = 0;
minutes = 0;
clearInterval(time);
}
playBtn.addEventListener("click", () => {
introScreen.classList.remove("fadeIn");
introScreen.classList.add("fadeOut");
game.classList.add("fadeIn");
startGame();
});
What you're running into is a common problem where it takes time for things like CSS to propagate through the website, especially when you have timeouts. What you should do is VERY RARELY rely on CSS classes for your logic and let CSS be what it's meant to be: a purely visual medium.
The solution is simple. Instead of relying on pointer events, set up a flag that toggles whether the user is allowed to click on something or not, and then reference that flag for all your logic. This allows you to decouple what you see from what's happening underneath the hood. Something like this (only the relevant bits):
let canAction = true; // add a flag
function startGame() {
// ... start game logic
canAction = true; // set the flag to allow actions
}
gameContainer.addEventListener("click", function (e) {
if (canAction) {
// ... card click logic
}
});
function compareCards() {
if (openCards.length == 2) {
canAction = false; // stop user from taking further action
}
// ... rest of compare card logic
}
function cardsMatched() {
canAction = true; // re allow the user to click on cards - put this inside the setTimeout or outside depending on what you need
setTimeout(function () {
// ...
}, 500);
countMoves();
}
function cardsNotMatched() {
canAction = true; // re allow the user to click on cards - put this inside the setTimeout or outside depending on what you need
setTimeout(function () {
// ...
}, 500);
countMoves();
}
Of course you are free to keep the pointer-events CSS stuff as well, but don't rely on it for your logic. You'll just be inviting a whole lot of messy situations like this.
I have several functions that use this given for loop below.
function startClaw(dir){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
isAnimating = $("#claw").is(':animated');
if(!isAnimating){// prevents multiple clicks during animation
if(isMoving || isDropping){ return; }
MCI = setInterval(function(){ moveClaw(dir); },10);
//console.log("startClaw:" + dir);
stopSwingClaw();
}
}
}
}
//.................................................................
function dropClaw(){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
if(isDropping){ return; } //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
}
}
Everything in the else if statement is different within the various functions. I'm wondering if there is any way to place the "pieces" of the for loop on the outside of the else if into its very own function. I feel like I've seen this or had done this a very long time ago, but it escapes me and I couldn't find any examples. Thanks everyone!
Previewing, I see this is similar to the above. Two differences (it looks like) are here the count gets passed to the function in case they needed to ever have different checks in the if statement, and, it's checking what the return value is since it looks like you return out of the loop if the condition is met. There are notes in comments in the code below.
function startClaw(dir) {
// Pass a function as a callback to the method which expects to receive the count as a param
doReadCount(qdata, function(theCount) {
if (theCount === 5) {
isAnimating = $("#claw").is(':animated');
if (!isAnimating) { // prevents multiple clicks during animation
if (isMoving || isDropping) {
return true;
}
MCI = setInterval(function() { moveClaw(dir); }, 10);
//console.log("startClaw:" + dir);
stopSwingClaw();
}
return false;
});
}
//.................................................................
function dropClaw() {
// Pass a function as a callback to the method which expects to receive the count as a param
doReadCount(qdata, function(theCount) {
if (theCount === 5) {
if (isDropping) {
return;
} //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
});
}
function doReadCount(qdata, elseFunction) {
var readCount = 0;
var elseReturn;
for (var isRead in qdata) {
readCount++;
if (qdata[isRead]['reading'] == true) {
return;
} else {
// call the function that was sent and pass it the current read count. If the return is true, then also return true here
elseReturn = elseFunction(readCount);
if (elseReturn) {
return;
}
}
}
}
You can pass a function into another function to achieve this. I've done it for dropClaw, and it should be clear from my example how to do also extract startClaw.
function operateClaw(func){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
func();
}
}
}
function drop () {
if(isDropping){ return; } //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
function dropClaw () {
operateClaw(drop);
}
I was wondering why my program crashes after its made its first match....any ideas would be greatly appreciated. Below is the code snippet. Thanks for the input!
var clicks = 0; //counts how may picks have been made in each turn
var firstchoice; //stores index of first card selected
var secondchoice; //stores index of second card selected
var match = 0; //counts matches made
var backcard = "deck.jpg"; //shows back of card when turned over
var faces = []; //array to store card images
faces[0] = 'pic1.jpg';
faces[1] = 'pic2.jpg';
faces[2] = 'pic3.jpg';
faces[3] = 'pic3.jpg';
faces[4] = 'pic2.jpg';
faces[5] = 'pic1.jpg';
function choose(card) {
if (clicks === 2) {
return;
}
if (clicks === 0) {
firstchoice = card;
document.images[card].src = faces[card];
clicks = 1;
} else {
clicks = 2;
secondchoice = card;
document.images[card].src = faces[card];
timer = setInterval("check()", 1000);
}
}
/* Check to see if a match is made */
function check() {
clearInterval(timer); //stop timer
if (faces[secondchoice] === faces[firstchoice]) {
match++;
document.getElementById("matches").innerHTML = match;
} else {
document.images[firstchoice].src = backcard;
document.images[secondchoice].src = backcard;
clicks = 0;
return;
}
}
The first parameter of setInterval needs to be a function not a string pretending to be a function. So you would want this:
timer = setInterval(function() { check(); }, 1000);
Of course, you can simplify:
timer = setInterval(check, 1000);
Not sure why you're using setInterval() here. You could more easily just do:
timer = setTimeout(check, 1000);
The advantage is there is no interval to clear in the check() function.
The other issue is that you are not resetting your 'clicks' counter to 0 when there is a match.
You want this:
function check() {
clearInterval(timer); //stop timer
if (faces[secondchoice] === faces[firstchoice]) {
match++;
document.getElementById("matches").innerHTML = match;
} else {
document.images[firstchoice].src = backcard;
document.images[secondchoice].src = backcard;
}
clicks = 0;
}
I think you have to declare you timer function globally. Its only defined in the scope of the first function, so in the second when you try to clear it nothing happens:
var timer = ''; //Declare timer up here first!
function choose(card) { ... }
function check() { ... }
this.update = function() {
if (state == "game") {
if (jaws.pressed("p") && !jaws.paused) {
jaws.paused = true;
setTimeout(function() {
var unpause_interval_id = setInterval(function() {
if (jaws.pressed("p") && jaws.paused) {
jaws.paused = false;
clearInterval(unpause_interval_id);
}
});
}, 5000);
}
That is my attempt at pausing a game I'm working on. Basically, when the player pauses, I set a timeout of 5 seconds to a function that checks if the player wants to unpause.
However, I am not being successful at clearing the interval, the clearInterval function isn't work, I'm sure of it from what I've debugged. Any idea of what I'm doing wrong?
Thank you!
How about:
function p_unp()
{
jaws.paused = !jaws.paused;
}
jaws.on_keypress("p", p_unp);
I recommend to remove setInterval function. The while loop instead:
this.update = function() {
if (state == "game") {
if (jaws.pressed("p") && !jaws.paused) {
jaws.paused = true;
while (jaws.paused) {
if (jaws.pressed("p") && jaws.paused) {
jaws.paused = false;
}
}
});
}