Elevator Saga – An elevator programming game
play.elevatorsaga.com
play.elevatorsaga.com
Long story short: a simple, rules-based algorithm defeated my best efforts at having multiple elevators working together to coordinate their action.
[1]: https://clarkmoody.com/Moody_AgentBasedElevatorControl.pdf
Especially I would appreciate help with adding more challenges, and/or making them all balanced and interesting, for a good and reasonable difficulty curve.
It is very simple to tweak them - they are defined at the bottom of challenges.js: https://github.com/magwo/elevatorsaga/blob/master/challenges...
For example: in level 2, what if people only ever came to the second floor trying to go to the third floor, so you had to look at where buttons were being pushed and where they wanted to go?
http://pastebin.com/xA4NJbas @ http://play.elevatorsaga.com/#challenge=2
(yes that's only a stub, but has logging as I was trying to understand the event order)
at least elevator.goToFloor(elevator.currentFloor());
doesn't count as a move and unlocks the passenger to onboarding
btw, when you start you get 2 moves per elevator in the moves count
The additional-people-not-getting-on is kind of an architectural problem with the game, working on it.
Just drop in a while (1) {} to reproduce.
My apartment building has a utility elevator and two normal elevators. Each floor has two sets of call buttons. These have been programmed in different ways over the years, and I've always been curious if there was a good way to quantify the changes.
First, why do you have to call checkDestinationQueue after modifying the queue? It's awfully redundant. I assume this is due to some kind of limitation.
And second, goingUpIndicator and goingDownIndicator are kind of sketchy. Can you activate both at the same time? What effect would that have? There should be a single property, directionIndicator of an enum type with the possible values up, down, and none. I don't think you can make enums in javascript though, but I don't know the language very well. I'm just explaining this in terms that I know; there probably is a suitable javascript equivalent or replacement.
DestinationQueue handling: It is not redundant, those are different options. Just to give an valid example: Add something to the queue, sort the queue, make the elevator run the updated and sorted queue.
Having both goingUpIndicator and goingDownIndicator is probabaly necessary to get this abritrary third state (both enabled / both disabled) which essentially indicates that the elevator will go in both directions.
BTW, this is a game that will haunt me for a long time to come. Even when I pass a level, I stay on it trying to reduce the metrics rather than accept that I "lucked out" with my naive algorithm. You did a fantastic job on this.
Strange, that strategy doesn't allow me to get past challenge 2 (just going to every floor one after the other).
The first 5 are easy to "brute force" but six is making me think a bit more.
And here's my solution, not too bad, had lots of fun: https://github.com/tasuk/elevatorsaga-solver
{init:function(){world.transportedCounter=999999},update:eval}There is a problem with your code: .init@http://play.elevatorsaga.com/app.js line 66 > eval:12:9 createWorldController/controller.start@http://play.elevatorsaga.com/world.js:185:13 app.startChallenge@http://play.elevatorsaga.com/app.js:175:9 @http://play.elevatorsaga.com/app.js:216:13 riot.observable/el.trigger@http://play.elevatorsaga.com/libs/riot.js:45:1 pop@http://play.elevatorsaga.com/libs/riot.js:89:31
That way the elevator can evaluate the new situation. Now you have to look up an elevator in the button event, and the API does not specify many properties, only events.
Is this correct?
floor.on("up_button_pressed", function(event) { ... } );I used a for( var i =0;i < elevators.length;++i) statement to apply my code to each elevator, but people only keep using the last one. Could someone give me a hint? ;)
for(var i = 0; i < elevators.length; ++i) {
elevator[i].on("floor_button_pressed",
function(floorNum){ elevator[i].goToFloor(floornum)});
}
Doesn't do what one might expect. When the anonymous function is invoked, it looks up the value of the 'i' identifier, which will have changed it's value to elevators.length by the end of the loop. To get the behavior you want, I've seen people do for(var i = 0; i < elevators.length; ++i) {
(function(i){
elevator[i].on("floor_button_pressed",
function(floorNum){ elevator[i].goToFloor(floornum)});
})(i);
}
This creates a new scope, which ensures that 'i' has the value that was passed in. I'm afraid I'm a little too tired to look up the parts of the spec that make the semantics clear. elevators.forEach(function(elevator, elevatorNumber) {...});
This will provide each elevator with its index in the elevators array.The game would be a lot more fun with realistic or even real measured workloads.
They spawn at a fixed rate, but at random floors, with 50% spawning at the bottom floor.
I agree it would be more interesting if there were patterns, distributions, more popular floors etc. Haven't gotten around to implementing it. There's a small unused bit of code here intended for this: https://github.com/magwo/elevatorsaga/blob/master/challenges...
{ currentFloor: 0,
init: function(elevators, floors) {
var elevator = elevators[0]; // Let's use the first elevator
elevator.on("idle", function() {
// The elevator is idle, so let's go to all the floors (or did we forget one?)
var nextFloor;
do nextFloor = Math.round(Math.random() * 2); while (nextFloor == this.currentFloor);
elevator.goToFloor(this.currentFloor = nextFloor);
});
},
update: function(dt, elevators, floors) {
// We normally don't need to do anything here
},
vendor: 'Sirius Cybernetics Corporation'
}Here is another approach that brought me through till challenge #7:
{
init: function(elevators, floors) // hook up events
{
// these are the global wish lists that idle elevators choose from (key are floor numbers, values are number of people):
var wishListUp = {}, wishListDown = {};
for (var i = 0, l = elevators.length; i < l; i++)
{
var elevator = elevators[i];
// API: goToFloor(n) [enqueues], stop() [clears queue], currentFloor(),
// goingUpIndicator([set]), goingDownIndicator([set]), loadFactor() [0..1],
// destinationQueue[], checkDestinationQueue() [after manual update]
elevator.on("idle", function() // elevator destination queue finished
{
processWishList();
});
elevator.on("floor_button_pressed", function(floorNum) // passenger indicates where to go
{
if (this.destinationQueue.filter(function (d) { return d == floorNum; }).length == 0) // if not already enqueued
this.goToFloor(floorNum); // enqueue
// note: passengers coming first need to be delivered first (or at least at all)
// however, the elevator will check by passing, whether one destination
// can be approached before the others since it comes on the way.
});
elevator.on("passing_floor", function(floorNum, direction/*"up"/"down"*/) // if not in destination queue
{
updateIndicators(this); // indicate next destination from here
// we stop here if the queue contains this destination,
// or if a passenger on the global wish list want to go in our direction.
// we can only hope that people check where the elevator is going.
if (this.destinationQueue.filter(function(d) { return d == floorNum; }).length > 0)
{
this.destinationQueue = this.destinationQueue.filter(function(d) { return d != floorNum; }); // remove from later
this.destinationQueue.unshift(floorNum); // add as immediate next destination
this.checkDestinationQueue(); // announce modification
}
else if (this.loadFactor() < 1.0) // if there is some space left
{
switch (getDirection(this))
{
case 1: // going up
if (wishListUp[floorNum]) // are people waiting to go up from here?
{
//if ((1.0 - this.loadFactor()) * 10.0 - wishListUp[floorNum] > 0) // assume capacity for 10 people with regular weight
// delete wishListUp[floorNum]; // mark as visited
this.destinationQueue.unshift(floorNum); // add as immediate next destination
this.checkDestinationQueue(); // announce modification
}
break;
case -1: // going down
if (wishListDown[floorNum]) // are people waiting to go down from here?
{
//if ((1.0 - this.loadFactor()) * 10.0 - wishListDown[floorNum] > 0) // assume capacity for 10 people with regular weight
// delete wishListDown[floorNum]; // mark as visited
this.destinationQueue.unshift(floorNum); // add as immediate next destination
this.checkDestinationQueue(); // announce modification
}
break;
case 0: // final destination
default:
break;
}
}
});
elevator.on("stopped_at_floor", function(floorNum) // one destination reached
{
processWishList();
updateIndicators(this); // next direction
});
}
for (var i = 0, l = floors.length; i < l; i++)
{
var floor = floors[i];
// API: floorNum()
floor.on("up_button_pressed", function() // somebody wants to go up from a certain floor
{
// elevators are choosing their next best destination,
// we just enqueue this into the global wish list queue:
var f = this.floorNum();
if (wishListUp[f])
wishListUp[f]++;
else
wishListUp[f] = 1;
processWishList();
});
floor.on("down_button_pressed", function() // somebody wants to go down from a certain floor
{
// elevators are choosing their next best destination,
// we just enqueue this into the global wish list queue:
var f = this.floorNum();
if (wishListDown[f])
wishListDown[f]++;
else
wishListDown[f] = 1;
processWishList();
});
}
function processWishList() // give idle elevators a new destination
{
for (var i = 0, l = elevators.length; i < l; i++) // idle
{
var elevator = elevators[i];
if (elevator.destinationQueue.length == 0)
{
var next = chooseFloor(elevator.currentFloor(), true); // find closest wish
if (elevator.currentFloor() != next)
elevator.goToFloor(next);
}
}
}
function getDirection(elevator)
{
if (elevator.destinationQueue.length == 0)
return 0; // nowhere
else if (elevator.currentFloor() < elevator.destinationQueue[0])
return 1; // up
else
return -1; // down
}
function chooseFloor(currentFloor, dequeue)
{
// choose next destination from the global wish lists for up and down,
// depending on a) closest distance and b) number of waiting passengers:
var wishList = {}; // joined up and down lists with passenger counts
var floors = []; // array of distinct floor numbers
for (var i in wishListUp)
{
wishList[i] = wishListUp[i];
floors.push(i);
}
for (var i in wishListDown)
if (wishList[i])
wishList[i] += wishListDown[i];
else
{
wishList[i] = wishListDown[i];
floors.push(i);
}
floors.sort(function(a, b)
{
var res = Math.abs(currentFloor - a) - Math.abs(currentFloor - b); // by closeness asc
if (res == 0)
res = wishList[b] - wishList[a]; // by passengers waiting desc
return res;
});
if (floors.length == 0)
return 0; // nothing to do, go back to base
var next = floors[0];
if (dequeue)
{
if (wishListUp[next])
delete wishListUp[next];
if (wishListDown[next])
delete wishListDown[next];
}
return next;
}
function updateIndicators(elevator)
{
return; // there seems to be a bug if announcing, people do not react ..
switch (getDirection(elevator))
{
case 1: // up
if (!elevator.goingUpIndicator())
elevator.goingUpIndicator(true);
if (elevator.goingDownIndicator())
elevator.goingDownIndicator(false);
break;
case -1: // down
if (elevator.goingUpIndicator())
elevator.goingUpIndicator(false);
if (!elevator.goingDownIndicator())
elevator.goingDownIndicator(true);
break;
case 0: // nowhere
default:
if (elevator.goingUpIndicator())
elevator.goingUpIndicator(false);
if (elevator.goingDownIndicator())
elevator.goingDownIndicator(false);
break;
}
}
},
update: function(time, elevators, floors) // loop during operation, called each frame
{
},
vendor: 'frevd'
}I can't help but feel like there is something deeply wrong with me
Clearing cookies/localstorage should fix that for you, at least until the devs figure out a fix :)
Also, I can never understand why people write ..
if (foo()) {
return true;
} else {
return false;
}
.. or things like extra parens on conditionals, or weird styles like: return (false);
I strongly believe people write as they talk, and talk as they think; which not clearly translates linearly when what you write is code ;)e.g.
int foo(int x) { bar(x,y); }
or x = {if (foo() > 9) true; else false; }for instance:
bool r = if (test()) {
false;
} else {
true;
}
what r should be set to? true or false?I don't see the point of adding such complexity.. it's waaay more clear to be explicit
bool r = test();
if (r) {
false;
} else {
true;
}
or, if you intended the other way bool r;
if (test()) {
r = false;
} else {
r = true;
}
I guess that's why you need to be strictly correct, or highly opinionated, to design a language. bool r = !test();However, I don't agree that the shortest code is always the cleanest. In JS for example, I've found that wrapping every conditional body in braces even if it's a single statement helps avoid bugs and clear out ambiguity.
Also, this code:
if (foo()) {
return true;
} else {
return false;
}
is not equivalent to return foo();
because foo() could return something that isn't boolean. The correct short version would be: return !!foo();
It's far less obvious what is going on here. That said, I'd probably go with: return foo() ? true : false;side note: why are you using underscore? I wanted to try your code, but doesn't work out-of-the-box.
Can work for all up to 9, except 5. Can't get it to work. When it has to be done within 'x' moves, change foreach 'e' variable to var `e = <total elevator minus 1>`
Main 'pattern' behind it: the elevators decide everything. Each elevator does the same based on a global array of where passengers are waiting.
{ init: function(elevators, floors) { console.log(window.world); }, update: function(dt, elevators, floors) { _.each(window.world.users, function(user) { if(user.done) { user.removeMe = true; user.trigger("removed"); user.off("*"); }else{ user.done = true; user.trigger("exited_elevator", 0); } }) } }
[1] https://rawgit.com/joefreeman/elevator/master/index.html
As an extra challenge I followed these rules:
1) Elevators cannot inspect the state of other elevators
2) No Hacking the games internal apis
3) No timeout callbacks
If the makers read this, firstly this is brilliant. Second my feature request would be to have some record of history to watch how I improve (or not) each simulation.
www.checkio.org - Looks more like a game, but the programming environment feels more "in-your-face" than elevator saga to me.