I'am working on a game with the canvas element. The goal is that first time you press a key it start a stopwatch. It should end as soon as the gameoverscreen/winscreen appears.
After the gameoverscreen/winscreen it should work like befor(if press key than start stopwatch)
The problem is that that the function only once called can be.
The Code(the most important part):
function startTime(){
startTime = function(){};
var count = 0;
function stopwatch(){
if(winScreen || gameOver){
count = 0;
} else{
console.log(count++);
}
}
setInterval(stopwatch, 1000);
}
document.addEventListener('keydown', function(event){
startTime();
});
Is there a way to solve that problem?
The cause of your problem is that you are overwriting startTime with an empty function on the second line. The second time you call startTime(), it runs the empty function.
To keep your code clean, your stopwatch shouldn't really check for the win or game over conditions - it should only keep track of the count. The rest of your game code can start and reset the stopwatch whenever those conditions occur. You could have a stopwatch object like this:
var stopwatch = {
count: 0,
intervalId: null,
start: function() {
stopwatch.intervalId = setInterval(function() {
stopwatch.count++;
}, 1000)
},
reset: function() {
if (stopwatch.intervalId) {
clearInterval(stopwatch.intervalId);
stopwatch.intervalId = null;
}
stopwatch.count = 0;
}
}
Then your game can call stopwatch.start() when it starts and stopwatch.reset() when it ends.
Note that it also clears the interval when it resets. Without this, the function inside setInterval would get duplicated every time, causing potential bugs and a memory leak.
Related
I'm trying to animate a camera rotation using MapBoxGL, while providing the option to pause the rotation and restart the rotation with a checkbox callback. The 'pause'/'stop' rotation works fine, but the 'restart' seems to pick up the animation where the it should have been if it was never paused, as opposed to picking up where the animation stopped.
var animation;
function rotateCamera(timestamp) {
map.rotateTo((timestamp / 600) % 360, {duration: 0});
animation = requestAnimationFrame(rotateCamera);
}
When the map loads, the animation is called with:
animation = rotateCamera(0);
The callback looks like this:
d3.selectAll("input[name='camerarotation-selection']").on("change", function() {
if (d3.select("input[name='selection']").property("checked")) {
rotateCamera(map.getBearing());
} else {
cancelAnimationFrame(animation);
}
});
If I console.log the timestamp var inside the rotateCamera function, I can see that despite the cancelAnimationFrame call, it continues to be incremented. I have tried declaring animation to be undefined upon a restart, and that doesn't seem to work either. I'm stumped! Thanks for your help.
The timestamp passed to the callback of requestAnimationFrame is an DOMHighResTimestamp, similar to the one returned by performance.now().
This timestamp indicates the number of milliseconds that elapsed since the beginning of the current document's lifetime (well it can be a bit more complicated) when the callbacks execution started.
So even when no requestAnimationFrame loop is running, this timestamp indeed increments, just like Date.now() also does.
let animation = 0;
inp.onchange = e => {
if (inp.checked) start();
else {
cancelAnimationFrame(animation);
}
};
function start(timestamp) {
_log.textContent = timestamp;
animation = requestAnimationFrame(start);
}
<input type="checkbox" id="inp">
<pre id="_log"></pre>
In your code, you will probably want to only keep the time that elapsed since last frame. To do so, you can simply save timestamp in a variable that is globally available, and then in your callback do
var elapsed = timestamp - last_frame;
last_frame = timestamp;
And remember to also take care of the resume case, where timestamp will be undefined and elapsed should be reset.
Now, I'd like to point out that your description of the problem could also indicate an other problem entirely: you could have more than a single loop running simultaneously.
Since you are using a single animation variable to hold the frame_id (used by cancelAnimationFrame), if you do call rotateCamera while a loop is already running, the first frame_ids will get lost, and their rAF loop will indeed continue.
let common_id = 0; // OP's `animation` variable
let first_id = 0;
let second_id = 0;
function loop1(timestamp) {
common_id = first_id = requestAnimationFrame(loop1);
log_1.textContent = "loop 1: " + timestamp;
}
function loop2(timestamp) {
common_id = second_id = requestAnimationFrame(loop2);
log_2.textContent = "loop 2: " + timestamp;
}
btn.onclick = e => {
console.log("first loop's id", first_id);
console.log("second loop's id", second_id);
console.log('clearing common_id', common_id);
cancelAnimationFrame(common_id);
}
loop1();
loop2();
<button id="btn">stop the loop</button>
<pre id="log_1"></pre>
<pre id="log_2"></pre>
I think it is possible in your code, since input[name='camerarotation-selection'] could change multiple times, when input[name='selection'] had not chnaged, or even since input[name='camerarotation-selection'] could be multiple elements.
To avoid that, you could keep a semaphore variable allowing you to know if the loop is running or not, and to only start it when it's not.
Or you could even get rid entirely of cancelAnimationFrame by using only one semaphore, and exiting early in the rAF callback:
let stopped = true;
function loop(timestamp) {
if (stopped) return; // exit early
requestAnimationFrame(loop);
log.textContent = timestamp;
}
// you can click it several times
btn_start.onclick = e => {
if (stopped === true) { // only if not running yet
stopped = false;
requestAnimationFrame(loop);
}
}
btn_stop.onclick = e => {
stopped = true; // deal only with the semaphore
};
btn_switch.onclick = e => {
stopped = !stopped;
if (stopped === false) { // we were paused
// start again
requestAnimationFrame(loop);
}
}
<button id="btn_start">start the loop</button>
<button id="btn_stop">stop the loop</button>
<button id="btn_switch">switch the loop</button>
<pre id="log"></pre>
Let's say I have a function:
myFunc = function(number) {
console.log("Booyah! "+number);
}
And I want it to run on a set interval. Sounds like I should use setInterval, huh!
But what if I want to run multiple intervals of the same function, all starting at the exact same time?
setInterval(function(){
myFunc(1);
}, 500);
setInterval(function(){
myFunc(2);
}, 1000);
setInterval(function(){
myFunc(3);
}, 2000);
So that the first runs exactly twice in the time it takes the second to run once, and the same between the second and third.
How do you make sure that they all start at the same time so that they are in sync?
Good question, but in JS you can't. To have multiple functions in the same program execute at the same time you need multi-threading and some deep timing and thread handling skills. JS is single threaded. setInterval doesn't acutally run the function after the delay, rather after the delay it adds the function to the event stack to be run as soon as the processor can get to it. If the proc is busy with another operation, it will take longer than the delay period to actually run. Multiple intervals/timeouts are all adding calls to the same event stack, so they run in turn as the proc is available.
function Timer(funct, delayMs, times)
{
if(times==undefined)
{
times=-1;
}
if(delayMs==undefined)
{
delayMs=10;
}
this.funct=funct;
var times=times;
var timesCount=0;
var ticks = (delayMs/10)|0;
var count=0;
Timer.instances.push(this);
this.tick = function()
{
if(count>=ticks)
{
this.funct();
count=0;
if(times>-1)
{
timesCount++;
if(timesCount>=times)
{
this.stop();
}
}
}
count++;
};
this.stop=function()
{
var index = Timer.instances.indexOf(this);
Timer.instances.splice(index, 1);
};
}
Timer.instances=[];
Timer.ontick=function()
{
for(var i in Timer.instances)
{
Timer.instances[i].tick();
}
};
window.setInterval(Timer.ontick, 10);
And to use it:
function onTick()
{
window.alert('test');
}
function onTick2()
{
window.alert('test2');
}
var timer = new Timer(onTick, 2000,-1);
var timer = new Timer(onTick2, 16000,-1);
For a finite number of ticks, change the last parameter to a positive integer for number. I used -1 to indicate continuous running.
Ignore anyone who tells you that you can't. You can make it do just about any thing you like!
You can make something like this.
arr = Array();
arr[0] = "hi";
arr[1] = "bye";
setTimer0 = setInterval(function(id){
console.log(arr[id])
},1000,(0));
setTimer1 = setInterval(function(id){
console.log(arr[id]);
},500,(1));
Hope it helps!
JavaScript is single threaded. You can use html5 web worker or try using setTimeout recursively. Create multiple functions following this example:
var interval = setTimeout(appendDateToBody, 5000);
function appendDateToBody() {
document.body.appendChild(
document.createTextNode(new Date() + " "));
interval = setTimeout(appendDateToBody, 5000);
}
Read this article:
http://weblogs.asp.net/bleroy/archive/2009/05/14/setinterval-is-moderately-evil.aspx
You can use multiples of ticks inside functions, in the example below you can run one function every 0.1 sec, and another every 1 sec.
Obviously, the timing will go wrong if functions require longer times than the intervals you set. You might need to experiment with the values to make them work or tolerate the incorrect timing.
Set a variable to handle tick multiples
let tickDivider = -1
Increase the value of tick variable inside the faster function
const fastFunc = ()=> {
tickDivider += 1
console.log('fastFunciton')
}
Use a condition to on running the slower function
const slowFunc = ()=> {
if (!(tickDivider % 10)){
console.log('slowFunction')
}
}
Call both functions in a single one. The order is not important unless you set tickDivider to 0 (of any multiple of 10)
const updateAllFuncs = () => {
fastFunc()
slowFunc()
}
Set the interval to the frequency of the faster function
setInterval(updateAllFuncs, 100)
What I'm doing here is adding a speed attribute to the HTML elements. These speeds are passed as a parameter to setCounter(). I did this mainly to make the code easier to test and play with.
The function setCounter() is invoked inside a loop for every HTML element with class counter. This function sets a new setInterval in every execution.
The intervals seem to be working in sync.
const elements = document.querySelectorAll('.counter')
elements.forEach((el, i) => {
let speed = Number(elements[i].getAttribute('speed'))
setCounter(el, speed, 5000)
})
function setCounter(element, speed, elapse){
let count = 0
setInterval(() => {
count = (count >= elapse) ? elapse : count + speed
if(count === elapse) clearInterval()
element.innerHTML = count
}, 1)
}
Same Speeds
<p class="counter" speed='10'></p>
<p class="counter" speed='10'></p>
Different Speeds
<p class="counter" speed='3'></p>
<p class="counter" speed='5'></p>
I am trying to do a infinite loop, but it only works if I include an 'alert' on it. My code looks like this:
while( tocontinue ){
// Some code
alert('Accept to continue');
}
On this way, the user has to click to hide the alerts (for example, on Chrome), and then the loop continues correctly by itself. I need to implement this without any alert. I also tried this:
while( tocontinue ){
// Some code
tocontinue = false;
setTimeout(function(){tocontinue=true},500);
}
And with "window.setTimeout" too, and without the word "function(){}", but it doesn't work. I tried everything: some implementations on JavaScript of a sleep() function, calling the function each X time with setInterval, answers 1 and 3 on this post... :/
Thank you very much for your time.
I'm trying to implement a genetic algorithm, and I want to stop it when I decide (with a button that puts the global variable "tocontinue" to false). Meanwhile, I want a infinite loop.
Well, you won't be able to combine a true infinite loop with user interaction as they'll both be dependent on the same thread being able to work on them exclusively. But, you can get close with a near-instant interval.
var interval = setInterval(function () {
// some code
}, 10);
Possibly grouping a few iterations together for each round:
var interval = setInterval(function () {
var limit = 5;
while (limit--) {
// some code
}
}, 10);
But, the interval will keep the iteration going as quickly as possible while still giving some idle time for user interactions, like clicking a particular button to clear the interval.
document.getElementById('stopButton').addEventListener('click', function () {
clearInterval(interval);
}, false);
Example: http://jsfiddle.net/coiscir/xZBTF/
setInterval() may be more useful here.
function updateLoop() {
//All the code goes here
}
setInterval(updateLoop,500);
var reader = new XMLHttpRequest() || new ActiveXObject('MSXML2.XMLHTTP');
function loadFile() {
reader.open('get', 'ccc.txt', true);
reader.onreadystatechange = displayContents;
reader.send(null);
}
function displayContents() {
if(reader.readyState==4) {
var el = document.getElementById('main');
el.innerHTML = reader.responseText;
var data = el.innerHTML;
}
}
for(var I = 7; I >1; i+=3);
console.log(i)
fourI am writing a javascript for loop and am sure have done a terrible job:
init = function () {
var i = 0;
timer = setInterval(function () {
if (i >= 4) {
clearInterval(timer);
return;
}
for (i = 0; i < 10; i++) {
console.log('init fired');
}
}, 2000);
};
init();
What I want is for the timer to stop after the i variable in the for loop reaches four. Instead the log is showing init fired ten times. What am I doing wrong?
I think you need it like this
var i=0; //Global Declaration
init = function(){
timer = setInterval(function(){
console.log('init fired');
i++;
if(i>4){
clearInterval(timer);
return; }
}, 2000);
};
init();
Hope this solves your problem. This will trigger init() method four times as you have expected and if the i reaches 4 the interval will be cleared.
Every time the timeout handler runs, it starts "i" back at zero.
The problem with your "for" loop is basically that you should not use a "for" loop :-)
Those 10 iterations are happening on the first pass through the function. After that first pass, "i" will be 10 and so the "if" condition will cancel the timeout. However, that "if" check is only made at the beginning of the function, so the loop will always complete 10 iterations.
If you want to have just four iterations of the timer (or five or whatever), you'd just leave off the "for" loop, and add i++; after the console log message. That way, the timer would issue one log output when it runs, and when that's happened the number of times you desire, it will stop.
I've been playing around with a site, in which I want to continue clicking a button for i amount of times every interval seconds.
My code is:
clickbidBtn1 = function() {
var bidBtn=document.getElementById("BidButton");
var interval = 15000;
for (var i=3; i>=0; i--){
setTimeout(bidBtn.click(1);,i*interval);
};
I've found out that GM executes all i amount of clicks at the same time, not with the intended delay. is there a way to delay the time of click? Say i wanted the function to click the button every 15 second for i amount of times.
I was thinking of giving it some more variables, and adding one variable in the settimeout code part, which only executes # the click, then comparing increased variables with current ones before going to the next settimeout... but haven't thought it through yet... it seems to be a complicated process for a simple process... :( i wll play around with it a bit
Use setInterval() for this.
One way:
var bidClickTimer = 0;
var numBidClicks = 0;
function clickbidBtn1 ()
{
var interval = 15000;
bidClickTimer = setInterval (function() {BidClick (); }, interval);
}
function BidClick ()
{
numBidClicks++;
if (numBidClicks > 3)
{
clearInterval (bidClickTimer);
bidClickTimer = "";
}
else
{
bidBtn.click (1);
}
}
clickbidBtn1 ();
Alternatively, without using global vars:
function clickbidBtn1 ()
{
var interval = 15000;
this.numBidClicks = 0;
this.bidClickTimer = 0;
this.BidClick = function () {
numBidClicks++;
if (numBidClicks > 3)
{
clearInterval (bidClickTimer);
bidClickTimer = "";
}
else
{
bidBtn.click (1);
}
};
this.bidClickTimer = setInterval (function(thisScope) {thisScope.BidClick (); }, interval, this);
}
clickbidBtn1 ();
Just to explain why your code does not work: You are calling the .click method immediately (putting () after a function name calls the function) and actually passing the return value of that function to setTimeout. The for loop is so fast that everything seem to happen at the same time.
You have to pass a function reference to setTimeout, e.g. an anonymous function:
setTimeout(function() {
bidBtn.click(1);
}, i*interval);