Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript settimeouts on html whack a mole game

I've been trying to create a html whack a mole game in which a mole has a class added to it at a certain interval, another timeout function is then triggered giving the user 3 seconds to click the mole and remove the class before a check is carried out which determines if that mole still has the class attached to it.

here is a jsfiddle of my game : https://jsfiddle.net/gko9puqf/1/ and below is my javascript.

var score = 0;
var numberofpipes = 9;
var lastnum = 0;
var intervalseconds;
var interval;
var haslost = false;
var checkpipetimer;
var timeoutfunc;
var timeoutinit;
var timers = [];
var burstingpipes = {};
var timeoutinit = setTimeout(startaburst, 3000);
$('#scorecontainer').text(score);
//starts a bursting pipe
function startaburst() {
  clearTimeout(timeoutinit);
  if (score < 10) {
    intervalseconds = 2;
  } else if (score >= 10 && score < 25) {
    intervalseconds = 1.5;
  } else if (score >= 25 && score < 40) {
    intervalseconds = 1;
  } else if (score >= 40 && score < 60) {
    intervalseconds = 0.5;
  } else if (score >= 60) {
    intervalseconds = 0.25;
  } else if (score > 100) {
    intervalseconds = 0.1;
  }
  interval = intervalseconds * 1000;
  burstingpipe();
  //creating a loop with the new timeout value as the game gets harder.
  //also assigning it to the timeoutfunc variable so i can cancel the loop later.
  timeoutfunc = setTimeout(startaburst, interval);
}

//adds the bursting pipe attributes to the pipe intersections
function burstingpipe() {
  randomnum = Math.floor(Math.random() * 9) + 1;
  //cant be the same twice in case of overlapping
  if ((randomnum == lastnum) || $("." + randomnum).hasClass("burstingpipe")) {
    //if the random num is still valid after -1, -1
    if (((randomnum - 1) >= 0) && !($("." + (randomnum - 1)).hasClass("burstingpipe"))) {
      randomnum = (randomnum - 1);
      //add one to the random number
    } else if (((randomnum + 1) <= (numberofpipes)) && !($("." + (randomnum + 1)).hasClass("burstingpipe"))) {
      randomnum = (randomnum + 1);
    } else {
      burstingpipe();
    }
  }
  //make the lastnum the current number so we dont get 2 in a row
  lastnum = randomnum;
  randomdiv = $("." + randomnum);
  console.log(randomdiv.hasClass("burstingpipe"));
  //adds shake animation and red glow
  console.log(randomnum);
  randomdiv.addClass("burstingpipe");

  //setting a timeout of 3 seconds, so th user has 3 seconds to press each 
  //bursting pipe before it bursts.
  checkpipetimer = setTimeout(haspipeburst.bind(this, randomdiv), 3000);
}

//function to check if the pipe has burst.
function haspipeburst(pipecheck) {
  console.log(pipecheck);
  console.log(pipecheck.hasClass("burstingpipe"));
  //checking to see if the pipe still has the class attached after 3 seconds
  //and if the user has already lost.
  if (pipecheck.hasClass("burstingpipe")) {
    //if the pipe still has the class attached - game over.
    haslost = true;
    $("#result").text("you have lost");
    //stopping the loop.
    clearTimeout(timeoutfunc);
    //changing the background color to make it look like the pipe has broken.
    //(will possibly change to image in future)
    //$(".hitpoint").removeClass("burstingpipe");
    $(pipecheck).css("background-color", "#49c1e2");
  }
}

//when the user clicks a hitpoint the class is removed and they gain a point.
$(document).on('click', '.hitpoint', function() {
  if ($(this).hasClass("burstingpipe") && haslost == false) {
    $(this).removeClass("burstingpipe");
    score++;
    $("#scorecontainer").text(score);
  }
});

it works as expected up until the timeout gets significantly shorter (around a score of 40) and the moles glitch out as if the timeout was ignored.

I've been staring at the code for hours now and have made little progress so I am hoping you can help me out! i believe its something to do with the timeouts not being completed properly.

any help is greatly appreciated, thanks.

like image 654
Dale Causier Avatar asked Dec 22 '16 12:12

Dale Causier


1 Answers

A bit of a late addition, but was working a bit on this in-between other tasks. As stated a problem with starting multiple timers is that you need to remember the specific timers and not only the last one. In the code below that is done by keeping a 'bursting pipe' inside a single class (function) with its own timer.

Perhaps I went a bit overboard, but as also stated by others, I liked the game you made :) One of the changes is not looping through all pipes to get a pipes that's not bursting, but remove the pipe from available pipes once it's bursting. This also negates the need for numbering the divs. More details in the code-comments. Of course you're free to ignore this code completely, but since I had it about finished, am posting it anyway.

Fiddle

var score = 24; //set higher for testing purposes
var pipes = $('.hitpoint').toArray() ,
	last = null,
  haslost = false,
	interval = 2, //start interval
	thresholds = {10: 1.5, 25: 1 , 40: 0.5, 60:0.25, 100 :1}; //interval thresholds
setTimeout(startaburst, 3000); //intial timeout (doesn't need to be cleared, because it's fired once)

$('#scorecontainer').text(score);
//starts a bursting pipe
function startaburst() {
	if(haslost)return; //already lost
  
  if(pipes.length>0){ //pick a pipe to burst unless all pipes allready bursting
    var i;
    while(true){
      var p = pipes[i = Math.floor(Math.random() * pipes.length)]; //get random element from the available pipes
      if(p!==last || pipes.length === 1)break;
    }  
    pipes.splice(i,1); //remove pipe from available pipes
    last = p; //remember last to prevent reusing the same pipe twice
    new burstingPipe(p);
	}
  
  setTimeout(startaburst, interval * 1000); //wait until staring the new burst. interval is increased inside backInGame if the score increases
}

function burstingPipe(pipe){
	this.pipe = $(pipe);
  this.pipe.addClass("burstingpipe");  
  
  function checkBurst(){  
  	this.dispose();
  	if(haslost)return; //already lost on other pipe
    haslost = true;
    $("#result").text("you have lost");
    //changing the background color to make it look like the pipe has broken.
    //(will possibly change to image in future)    		
    this.pipe.css("background-color", "#49c1e2");
  };
  
  this.dispose=function(){
  	this.pipe.off('click'); //unbind click (no longer bursting or already burst)
  	this.pipe.removeClass("burstingpipe");    
  }
  
  function backInGame(){  	
  	clearTimeout(this.timer); //clear the burst timeout (specific for this pipe)
    this.dispose();
    pipes.push(this.pipe[0]); //make pipe available again (NB, because the array contains of DOM elements and not jquery objects, [0] is needed)
    var int = thresholds[++score]; //increase the score and check if interval should be increased for the new score
    if(int && int < interval){ 
    	//optional: some message or css that interval is increased
    	interval =int;
    }
    $("#scorecontainer").text(score);
  }
  
  this.pipe.click(backInGame.bind(this)); //bind the click
  this.timer =setTimeout(checkBurst.bind(this), 3000);
}
@keyframes shake {
  5%,
  15%,
  25%,
  35%,
  45%,
  55%,
  65%,
  75%,
  85%,
  95% {
    left: 0;
    right: 1vh;
    outline: none;
    border-color: red;
    box-shadow: 0 0 10px red;
  }
  10%,
  20%,
  30%,
  40%,
  50%,
  60%,
  70%,
  80%,
  90%,
  100% {
    left: 1vh;
    right: 0;
    outline: none;
    border-color: red;
    box-shadow: 0 0 10px red;
  }
}

@-webkit-keyframes shake {
  5%,
  15%,
  25%,
  35%,
  45%,
  55%,
  65%,
  75%,
  85%,
  95% {
    left: 0;
    right: 1vh;
    outline: none;
    border-color: red;
    box-shadow: 0 0 10px red;
  }
  10%,
  20%,
  30%,
  40%,
  50%,
  60%,
  70%,
  80%,
  90%,
  100% {
    left: 1vh;
    right: 0;
    outline: none;
    border-color: red;
    box-shadow: 0 0 10px red;
  }
}

@-moz-keyframes shake {
  5%,
  15%,
  25%,
  35%,
  45%,
  55%,
  65%,
  75%,
  85%,
  95% {
    left: 0;
    right: 1vh;
    outline: none;
    border-color: red;
    box-shadow: 0 0 10px red;
  }
  10%,
  20%,
  30%,
  40%,
  50%,
  60%,
  70%,
  80%,
  90%,
  100% {
    left: 1vh;
    right: 0;
    outline: none;
    border-color: red;
    box-shadow: 0 0 10px red;
  }
}

@-o-keyframes shake {
  5%,
  15%,
  25%,
  35%,
  45%,
  55%,
  65%,
  75%,
  85%,
  95% {
    left: 0;
    right: 1vh;
    outline: none;
    border-color: red;
    box-shadow: 0 0 10px red;
  }
  10%,
  20%,
  30%,
  40%,
  50%,
  60%,
  70%,
  80%,
  90%,
  100% {
    left: 1vh;
    right: 0;
    outline: none;
    border-color: red;
    box-shadow: 0 0 10px red;
  }
}

html {
  height: 100%;
  width: 100%;
}

* {
  margin: 0;
  padding: 0;
}

body {
  height: 100%;
  width: 100%;
}

#gamecontainer {
  height: 100%;
  width: 100%;
  background-color: #49c1e2;
}

#gameinformation {
  height: 10%;
  display: flex;
  flex-direction: row;
  align-items: center;
  padding-left: 10%;
}

#pipecontainer {
  height: 80%;
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
}

.pipe {
  height: 8vh;
  width: 100vw;
  background-color: #a5a5a5;
  display: flex;
  flex-direction: row;
  justify-content: space-around;
}

.hitpoint {
  height: 10vh;
  width: 10vh;
  background-color: #6d6d6d;
  border-radius: 2vh;
  position: relative;
  bottom: 1vh;
  cursor: pointer;
}

#scoretext {
  color: #fff;
  font-size: 6vh;
}

#scorecontainer {
  color: #fff;
  font-size: 6vh;
}

#statusupdate {
  color: #fff;
  font-size: 6vh;
}

.burstingpipe {
  animation-name: shake;
  animation-duration: 3s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="gamecontainer">
  <div id="gameinformation">
    <p id="scoretext">Score:&nbsp;</p>
    <div id="scorecontainer">
    </div>
  </div>
  <div id="pipecontainer">
    <div class="pipe">
      <div class="hitpoint"></div>
      <div class="hitpoint"></div>
      <div class="hitpoint"></div>
    </div>
    <div class="pipe">
      <div class="hitpoint"></div>
      <div class="hitpoint"></div>
      <div class="hitpoint"></div>
    </div>
    <div class="pipe">
      <div class="hitpoint"></div>
      <div class="hitpoint"></div>
      <div class="hitpoint"></div>
    </div>
  </div>
  <div id="statusupdate">
    <p id="result"></p>
  </div>
</div>
like image 119
Me.Name Avatar answered Sep 28 '22 17:09

Me.Name