2

So, I'm creating a bot to play a video game online (completely legally, there's a server for bot competitions, don't worry). The code is in JavaScript, which I'm just picking up now, and I'm having trouble telling the bot to do something, then wait either by time or by condition until doing something else.

So far, I'm simply wrapping everything in gigantic setTimeout commands, but this is extremely inelegant.

There's not too much code that's relevant to post, but essentially I've made up a lot of code that does things like move to a specific location, brake, chase another player, etc. Most work with a recursive setTimeout command - press certain buttons, reset variables, then start the function again if the condition isn't met. If I want to move to a specific location then brake when I finish with that, generally I'd put code likes this:

moveTarget(target);
brake();
[...Execute rest of code...]

However, JavaScript executes both of these commands at the same time, which obviously doesn't work very well. Is there a way to make something more like this (Or the equivalent with a condition instead of a specified time):

moveTarget(target);
wait3Seconds();
brake();
[...Execute rest of code...]

Without needing to do this:

moveTarget(target);
setTimeout(function(){
    brake();
    [...Execute rest of code...]
},3000);

Given how many of these situations I'll have, my code will end up looking like a gigantic mess of nested setTimeouts, which is not very pretty. Is there a better way? I'm already using JQuery, so I'm perfectly willing to use that if necessary.

user2740392
  • 37
  • 1
  • 6
  • Where your js is running? In a browser? What happens in the `moveTarget`? – zerkms Apr 07 '14 at 22:27
  • try exploring callbacks? http://stackoverflow.com/questions/2190850/create-a-custom-callback-in-javascript – Joe Naber Apr 07 '14 at 22:30
  • You're looking for a blocking wait (A.K.A. [sleep](https://en.wikipedia.org/wiki/Sleep_(operating_system) ). This isn't possible in JS for a very good reason: when run in a browser, it would be very irritating if no buttons or UI work until the program finishes waiting. – rvighne Apr 07 '14 at 22:39
  • @zerkms: My JS is simply running in the browser - for now I copy-paste it into the console window of Chrome. In moveTarget, the program finds the bot's current location, compares that to the location of the target, then presses arrow keys corresponding to the direction it needs to go (and releases the ones no longer needed). It then checks to see if it has reached the target, and if it has, exits, if not, then I use a setTimeout to recursively go back to the beginning of the function. – user2740392 Apr 08 '14 at 00:47
  • @rvighne: Yes, sleep is what I need. I'm quite familiar with Python, and I'm sort of learning JavaScript as I go here. Actually, would there be a way to use Python to simply send snippets of JavaScript code (and receive data packets) to the browser as necessary? – user2740392 Apr 08 '14 at 00:51

3 Answers3

4

There isn't really a better way to do that. The reason is that any sort of waiting is going to trigger a task that goes into the task queue. Since javascript is single threaded, it has to use these task queues and as a result it will just keep on executing instructions past the setTimeout if instructions are present.

In order to execute code after your delay, it has to be in the timeout callback or it will execute immediately.

Travis J
  • 81,153
  • 41
  • 202
  • 273
  • That's the technical reality, but you could fairly easily create some abstraction to make this more elegant. That's all the OP wanted really. – rvighne Apr 07 '14 at 22:41
  • 1
    @rvighne - Not sure how much more elegant it gets, each nested delay will require its own function. Perhaps you could take advantage of chaining to avoid the nesting, but it would still require a function for every "brake()" as described above. – Travis J Apr 07 '14 at 22:44
  • Or just passing `brake` as the function to be chained. – rvighne Apr 07 '14 at 23:18
4

A little background...

JavaScript does not handle concurrency in the same way as other programming languages you may be used to. Instead, JavaScript has only the one main thread, and instead has a queue for registering then handling events in order as they occur.

For this reason, it is very important to not do things that would block the thread for long periods of time (like a function such as Thread.sleep() would do). That is why you instead should use setTimeout or setInterval for things like this.

You could always create a custom sleep function to write code in the style you are requesting. This might be something like:

function sleep(millis) {
  var startTime = Date.now();
  while (Date.now() < startTime + millis) {}
}

But this would be VERY, VERY BAD. This would freeze the page while this sleep function is running.

Back to your problem...

You basically need to refactor your code to be more "eventful". Rather than having large, linear chunks of code within the same block, you should write your JavaScript code to use callbacks and timeouts.

So the last code block you showed is really the paradigm you should be following with JavaScript:

moveTarget(target);
setTimeout(function(){
  brake();
  [...Execute rest of code...]
},3000);

However, you can certainly clean things up a LOT, if instead of [...Execute rest of code...], you split that apart into other functions and call them from within your setTimeout callback. Of course, keeping your code blocks small and concise is a good rule of thumb in any programming language.


You might also interested in checking out the request animation frame function.

Levi Lindsey
  • 1,049
  • 1
  • 10
  • 17
  • 1
    Yeah, I tried the custom sleep function - it didn't work :P. It seems like this is what I'll end up going with, perhaps combined with Kevin's response a bit. Thanks for the help. – user2740392 Apr 08 '14 at 00:53
1

You could do this if you created all of your methods in a chain that had a wait command, similar to the jQuery queue system.

$("<div>").queue(function(next){
    // do a
    a();
    next();
}).delay(3*1000).queue(function(next){
    // after 3 seconds do b
    b();
    next();
}).delay(3*1000).queue(function(next){
    // after 3 more seconds do c
    c();
    next();
}).dequeue(); //.dequeue() starts it, only has to be called to make a queue that isn't currently running begin.

so all you would have to do is maintain the same chain and keep adding to it.

Kevin B
  • 94,570
  • 16
  • 163
  • 180