Form EL-3 · operating manual

Writing an algorithm

Everything your code can see and do, on one page. If you can read JavaScript you can be running something in five minutes.

01

The shape of it

Export an object of handlers. All of them are optional. Anything you hang on this survives between calls, which is where your state goes.

export default {
  setup(building) { /* once, before anyone arrives */ },
  call(event, world) { /* somebody pressed a hall button */ },
  stop(event, world) { /* a car finished loading */ },
  idle(event, world) { /* a car has run out of things to do */ },
  tick(world) { /* every tenth of a second */ },
}

Most good solutions end up living almost entirely in tick, replanning from scratch each time. The event handlers are the gentler way in.

02

What you are told

world.time          // seconds since the run started
world.floors        // how many floors, lobby included
world.calls         // [{ floor, direction, waitingFor }] — lit buttons only
world.elevators     // every car
world.random()      // seeded; the same sequence every run

A hall call is a floor and a direction. That is all. You are not told how many people are standing there, and you are not told where any of them are going until they are inside one of your cars and have pressed a button.

waitingFor is how long the button has been lit, not how long the people have been there. When a full car leaves somebody behind, they press again and it starts from zero. You can work out that it happened — the car was at capacity and the call came straight back — but nobody hands it to you.

03

A car

{
  id, floor,        // floor is fractional while moving, e.g. 7.35
  atFloor,          // integer, or null if between floors
  direction,        // 'up' | 'down' | null — which way it is actually moving
  indicated,        // 'up' | 'down' | null — the lantern
  doors,            // 'closed' | 'opening' | 'open' | 'closing'
  load, capacity,   // capacity is 8, always
  pressed,          // floors the people inside asked for
  queue,            // floors you told it to visit, in order
  committed,        // a floor it is already braking for, or null
  outOfService,
}
car.goTo(floor)

Add a stop to the end of the queue. Ignored if it is already queued.

car.setQueue([floors])

Replace the queue outright. Duplicates and floors that do not exist are dropped.

car.clear()

Empty the queue. A car that has already started braking still makes that stop.

car.indicate(direction)

Set the lantern to 'up', 'down' or null.

car.hold(seconds)

Keep the doors open a little longer. Capped at ten seconds.

04

The lantern is not decoration

People only get into a car that is showing the way they want to go. A car showing 'up' does not answer the down call at that floor, and the people waiting to go down stay exactly where they are.

Showing null lets anybody in, which is fine when you only have one car and disastrous when you have six — you end up carrying people in the wrong direction.

Two rules worth writing down. There is no down from the ground floor and no up from the top, so a car arriving at either has only one honest thing to show. And the lantern is fixed from the moment the doors start to open until they are shut again: set it before you arrive, because changing it mid-stop is ignored.

05

Physics you cannot argue with

Cars accelerate at 1.2 floors/s² up to 2.4 floors/s, and have to come to a complete stop before they can reverse. Once a car has begun braking for a floor, that stop is happening: committed tells you which floor, and nothing you do will make it skip.

A stop costs 0.8s to open, at least 2s of dwell, 0.6s for each person in or out, and 0.8s to close. A car that arrives somewhere with nobody to pick up or put down does not bother opening its doors — the mistake cost you the journey, not the door cycle.

06

What has been taken away

The language is all there — classes, generators, destructuring, whatever you like. What is gone is anything that reaches the outside world, or that would make two runs of the same algorithm disagree:

Atomics · Buffer · Bun · Date · Deno · EventSource · FormData · Function · Headers · Notification · Request · Response · SharedArrayBuffer · SharedWorker · WebAssembly · WebSocket · Worker · XMLHttpRequest · __dirname · __filename · addEventListener · alert · caches · clearInterval · clearTimeout · close · crypto · document · env · eval · exports · fetch · globalThis · importScripts · indexedDB · localStorage · location · module · navigator · open · performance · postMessage · process · queueMicrotask · removeEventListener · reportError · requestAnimationFrame · require · scheduler · self · sessionStorage · setImmediate · setInterval · setTimeout · structuredClone · window

Use world.time instead of Date, and world.random() instead of Math.random. This is not box-ticking: the grader runs your algorithm twice and compares the two runs, and one stray unrepeatable value means the submission does not count.

console.log works and goes to the panel under the editor.

07

Budgets

A single handler call may take 250ms, and the whole run may spend 20 seconds thinking. Both are generous — they exist to stop a runaway loop, not to make this a performance competition. The workbench is more forgiving still, so you find out your algorithm is slow by watching it rather than by being cut off.

If you do go over, the run stops and the submission scores zero for that building, with the reason shown on your submission page.