Rating
66.5
No. 1Per building
The Quiet Annexe
53.1
Morning Rush
84.4
Home Time
60.5
Lunch
62.4
The Tower
82.4
Bad Monday
56.0
Rating
66.5
No. 1Per building
The Quiet Annexe
53.1
Morning Rush
84.4
Home Time
60.5
Lunch
62.4
The Tower
82.4
Bad Monday
56.0
Everyone in the run, bucketed. The lines are the two thresholds you are scored on.
| Wait | People | Share |
|---|---|---|
| 0–15s | 9 | 15.8% |
| 15–30s | 9 | 15.8% |
| 30–45s | 7 | 12.3% |
| 45–60s | 5 | 8.8% |
| 60–75s | 3 | 5.3% |
| 90–105s | 1 | 1.8% |
| 150–165s | 2 | 3.5% |
| 180–195s | 1 | 1.8% |
| 195–210s | 4 | 7.0% |
| 210–225s | 1 | 1.8% |
| 225–240s | 3 | 5.3% |
| 240–255s | 1 | 1.8% |
| 255–270s | 1 | 1.8% |
| 270–285s | 3 | 5.3% |
| 300s+ | 7 | 12.3% |
Median and 90th percentile of how long each person waited, by the minute they got in.
| At | p50 | p90 | Waiting |
|---|---|---|---|
| 0s | 6.0s | 6.0s | 2 |
| 30s | 15.1s | 22.3s | 3 |
| 60s | 24.4s | 34.8s | 0 |
| 90s | 6.9s | 6.9s | 4 |
| 120s | 4.4s | 4.4s | 6 |
| 150s | 55.1s | 67.4s | 3 |
| 180s | 2.2s | 11.6s | 7 |
| 210s | 38.8s | 91.1s | 10 |
| 240s | 36.6s | 60.0s | 5 |
| 270s | 0.0s | 0.0s | 7 |
| 300s | 0.0s | 0.0s | 10 |
| 330s | 0.0s | 0.0s | 12 |
| 360s | 0.0s | 0.0s | 16 |
| 390s | 0.0s | 0.0s | 21 |
| 420s | 0.0s | 0.0s | 23 |
| 450s | 0.0s | 0.0s | 23 |
| 480s | 0.0s | 0.0s | 23 |
| 510s | 0.0s | 0.0s | 23 |
| 540s | 0.0s | 0.0s | 23 |
| 570s | 0.0s | 0.0s | 23 |
First seed in full
Everyone in the run, bucketed. The lines are the two thresholds you are scored on.
| Wait | People | Share |
|---|---|---|
| 0–15s | 208 | 75.6% |
| 15–30s | 60 | 21.8% |
| 30–45s | 7 | 2.5% |
Median and 90th percentile of how long each person waited, by the minute they got in.
| At | p50 | p90 | Waiting |
|---|---|---|---|
| 0s | 1.0s | 1.0s | 0 |
| 30s | 1.4s | 3.3s | 0 |
| 60s | 1.5s | 6.5s | 2 |
| 90s | 0.7s | 5.1s | 0 |
| 120s | 1.0s | 8.9s | 1 |
| 150s | 1.3s | 6.4s | 1 |
| 180s | 10.6s | 13.4s | 8 |
| 210s | 10.9s | 19.6s | 4 |
| 240s | 5.4s | 12.9s | 7 |
| 270s | 12.2s | 21.4s | 5 |
| 300s | 4.2s | 18.3s | 9 |
| 330s | 16.9s | 21.6s | 5 |
| 360s | 2.3s | 20.2s | 0 |
| 390s | 6.5s | 11.6s | 3 |
| 420s | 4.7s | 8.9s | 4 |
| 450s | 7.0s | 13.4s | 1 |
| 480s | 5.8s | 9.9s | 16 |
| 510s | 15.4s | 25.8s | 12 |
| 540s | 29.1s | 32.6s | 11 |
| 570s | 15.0s | 35.6s | 1 |
| 600s | 6.2s | 15.2s | 1 |
| 630s | 1.0s | 3.0s | 0 |
| 660s | 6.2s | 7.7s | 1 |
| 690s | 3.3s | 9.1s | 0 |
| 720s | 1.0s | 4.0s | 0 |
| 750s | 0.0s | 0.0s | 0 |
| 780s | 0.0s | 0.0s | 0 |
| 810s | 0.0s | 0.0s | 0 |
| 840s | 0.0s | 0.0s | 0 |
| 870s | 0.0s | 0.0s | 0 |
First seed in full
Everyone in the run, bucketed. The lines are the two thresholds you are scored on.
| Wait | People | Share |
|---|---|---|
| 0–15s | 183 | 60.8% |
| 15–30s | 38 | 12.6% |
| 30–45s | 9 | 3.0% |
| 45–60s | 21 | 7.0% |
| 60–75s | 4 | 1.3% |
| 75–90s | 1 | 0.3% |
| 90–105s | 4 | 1.3% |
| 105–120s | 1 | 0.3% |
| 120–135s | 2 | 0.7% |
| 135–150s | 10 | 3.3% |
| 180–195s | 3 | 1.0% |
| 195–210s | 1 | 0.3% |
| 225–240s | 1 | 0.3% |
| 240–255s | 2 | 0.7% |
| 255–270s | 1 | 0.3% |
| 285–300s | 2 | 0.7% |
| 300s+ | 18 | 6.0% |
Median and 90th percentile of how long each person waited, by the minute they got in.
| At | p50 | p90 | Waiting |
|---|---|---|---|
| 0s | 2.9s | 2.9s | 1 |
| 30s | 6.3s | 9.2s | 2 |
| 60s | 5.8s | 28.2s | 1 |
| 90s | 3.2s | 3.7s | 0 |
| 120s | 5.0s | 11.9s | 2 |
| 150s | 4.7s | 12.5s | 3 |
| 180s | 5.6s | 14.7s | 12 |
| 210s | 5.2s | 7.5s | 18 |
| 240s | 8.3s | 54.5s | 21 |
| 270s | 9.7s | 26.3s | 25 |
| 300s | 6.4s | 11.6s | 30 |
| 330s | 7.6s | 56.3s | 29 |
| 360s | 20.5s | 184.2s | 26 |
| 390s | 7.9s | 33.9s | 27 |
| 420s | 12.1s | 49.1s | 28 |
| 450s | 7.2s | 25.1s | 27 |
| 480s | 14.0s | 15.4s | 37 |
| 510s | 27.6s | 339.6s | 33 |
| 540s | 19.5s | 50.7s | 36 |
| 570s | 27.5s | 67.9s | 34 |
| 600s | 4.8s | 41.2s | 31 |
| 630s | 20.6s | 141.8s | 28 |
| 660s | 8.2s | 10.7s | 32 |
| 690s | 43.9s | 431.7s | 27 |
| 720s | 9.5s | 59.3s | 21 |
| 750s | 254.0s | 540.8s | 0 |
| 780s | 0.0s | 0.0s | 0 |
| 810s | 0.0s | 0.0s | 0 |
| 840s | 0.0s | 0.0s | 0 |
| 870s | 0.0s | 0.0s | 0 |
First seed in full
Everyone in the run, bucketed. The lines are the two thresholds you are scored on.
| Wait | People | Share |
|---|---|---|
| 0–15s | 183 | 49.5% |
| 15–30s | 56 | 15.1% |
| 30–45s | 43 | 11.6% |
| 45–60s | 36 | 9.7% |
| 60–75s | 18 | 4.9% |
| 75–90s | 21 | 5.7% |
| 90–105s | 7 | 1.9% |
| 105–120s | 4 | 1.1% |
| 120–135s | 1 | 0.3% |
| 135–150s | 1 | 0.3% |
Median and 90th percentile of how long each person waited, by the minute they got in.
| At | p50 | p90 | Waiting |
|---|---|---|---|
| 0s | 4.3s | 6.8s | 1 |
| 30s | 6.4s | 14.6s | 2 |
| 60s | 7.8s | 11.7s | 1 |
| 90s | 3.4s | 6.4s | 5 |
| 120s | 7.8s | 32.4s | 5 |
| 150s | 14.2s | 34.9s | 2 |
| 180s | 4.5s | 14.4s | 2 |
| 210s | 6.9s | 33.8s | 6 |
| 240s | 7.1s | 67.4s | 2 |
| 270s | 9.5s | 21.0s | 1 |
| 300s | 2.9s | 14.2s | 7 |
| 330s | 9.7s | 25.7s | 6 |
| 360s | 17.0s | 18.0s | 8 |
| 390s | 9.5s | 73.2s | 9 |
| 420s | 14.1s | 46.6s | 0 |
| 450s | 6.3s | 10.0s | 7 |
| 480s | 7.8s | 20.6s | 2 |
| 510s | 3.4s | 41.4s | 36 |
| 540s | 9.8s | 23.2s | 29 |
| 570s | 46.2s | 55.4s | 39 |
| 600s | 77.0s | 82.5s | 32 |
| 630s | 46.7s | 102.3s | 22 |
| 660s | 81.1s | 96.2s | 12 |
| 690s | 4.9s | 41.0s | 21 |
| 720s | 81.9s | 119.5s | 20 |
| 750s | 48.2s | 69.2s | 15 |
| 780s | 23.8s | 46.7s | 20 |
| 810s | 50.1s | 73.9s | 10 |
| 840s | 20.6s | 46.9s | 14 |
| 870s | 33.0s | 40.5s | 7 |
| 900s | 12.9s | 52.9s | 5 |
| 930s | 2.2s | 22.7s | 2 |
| 960s | 4.6s | 14.0s | 4 |
| 990s | 14.9s | 25.7s | 1 |
| 1020s | 5.1s | 6.6s | 7 |
| 1050s | 14.7s | 32.8s | 0 |
| 1080s | 0.0s | 0.0s | 0 |
| 1110s | 0.0s | 0.0s | 0 |
| 1140s | 0.0s | 0.0s | 0 |
| 1170s | 0.0s | 0.0s | 0 |
First seed in full
Everyone in the run, bucketed. The lines are the two thresholds you are scored on.
| Wait | People | Share |
|---|---|---|
| 0–15s | 383 | 61.5% |
| 15–30s | 160 | 25.7% |
| 30–45s | 40 | 6.4% |
| 45–60s | 18 | 2.9% |
| 60–75s | 7 | 1.1% |
| 75–90s | 7 | 1.1% |
| 90–105s | 4 | 0.6% |
| 120–135s | 2 | 0.3% |
| 135–150s | 2 | 0.3% |
Median and 90th percentile of how long each person waited, by the minute they got in.
| At | p50 | p90 | Waiting |
|---|---|---|---|
| 0s | 2.8s | 13.0s | 2 |
| 30s | 3.4s | 13.5s | 10 |
| 60s | 12.6s | 27.4s | 4 |
| 90s | 3.9s | 16.8s | 8 |
| 120s | 6.1s | 12.0s | 8 |
| 150s | 11.2s | 25.6s | 13 |
| 180s | 9.0s | 76.8s | 10 |
| 210s | 13.4s | 25.5s | 17 |
| 240s | 12.7s | 125.0s | 23 |
| 270s | 40.9s | 92.3s | 18 |
| 300s | 30.8s | 43.0s | 12 |
| 330s | 6.9s | 16.9s | 21 |
| 360s | 19.8s | 65.0s | 10 |
| 390s | 16.5s | 79.1s | 12 |
| 420s | 10.8s | 27.1s | 4 |
| 450s | 5.4s | 19.2s | 6 |
| 480s | 7.7s | 29.4s | 0 |
| 510s | 6.7s | 16.4s | 10 |
| 540s | 10.6s | 24.4s | 2 |
| 570s | 10.7s | 21.5s | 14 |
| 600s | 19.9s | 27.6s | 6 |
| 630s | 5.5s | 26.8s | 6 |
| 660s | 13.0s | 39.0s | 2 |
| 690s | 4.3s | 18.5s | 7 |
| 720s | 10.2s | 38.1s | 3 |
| 750s | 1.4s | 11.1s | 11 |
| 780s | 9.1s | 17.8s | 12 |
| 810s | 15.9s | 63.1s | 13 |
| 840s | 7.2s | 15.2s | 6 |
| 870s | 15.3s | 39.2s | 15 |
| 900s | 14.6s | 26.8s | 7 |
| 930s | 7.3s | 29.4s | 13 |
| 960s | 13.0s | 30.6s | 12 |
| 990s | 14.1s | 49.9s | 10 |
| 1020s | 17.9s | 101.3s | 19 |
| 1050s | 18.9s | 28.7s | 0 |
| 1080s | 0.0s | 0.0s | 0 |
| 1110s | 0.0s | 0.0s | 0 |
| 1140s | 0.0s | 0.0s | 0 |
| 1170s | 0.0s | 0.0s | 0 |
First seed in full
Everyone in the run, bucketed. The lines are the two thresholds you are scored on.
| Wait | People | Share |
|---|---|---|
| 0–15s | 84 | 51.5% |
| 15–30s | 25 | 15.3% |
| 30–45s | 14 | 8.6% |
| 45–60s | 5 | 3.1% |
| 60–75s | 1 | 0.6% |
| 135–150s | 1 | 0.6% |
| 150–165s | 3 | 1.8% |
| 180–195s | 2 | 1.2% |
| 195–210s | 2 | 1.2% |
| 210–225s | 4 | 2.5% |
| 225–240s | 1 | 0.6% |
| 240–255s | 3 | 1.8% |
| 255–270s | 6 | 3.7% |
| 270–285s | 6 | 3.7% |
| 285–300s | 2 | 1.2% |
| 300s+ | 4 | 2.5% |
Median and 90th percentile of how long each person waited, by the minute they got in.
| At | p50 | p90 | Waiting |
|---|---|---|---|
| 0s | 1.0s | 1.0s | 0 |
| 30s | 1.4s | 1.6s | 3 |
| 60s | 8.5s | 15.0s | 1 |
| 90s | 3.9s | 6.5s | 0 |
| 120s | 1.0s | 5.1s | 0 |
| 150s | 7.7s | 12.1s | 3 |
| 180s | 5.4s | 9.4s | 0 |
| 210s | 2.4s | 15.6s | 0 |
| 240s | 1.0s | 1.6s | 2 |
| 270s | 5.8s | 10.6s | 6 |
| 300s | 9.4s | 16.1s | 5 |
| 330s | 9.0s | 28.5s | 16 |
| 360s | 39.3s | 54.5s | 1 |
| 390s | 8.7s | 30.4s | 5 |
| 420s | 19.0s | 44.8s | 2 |
| 450s | 11.2s | 11.8s | 3 |
| 480s | 6.4s | 18.6s | 4 |
| 510s | 20.7s | 26.6s | 9 |
| 540s | 27.4s | 32.9s | 6 |
| 570s | 27.9s | 27.9s | 13 |
| 600s | 13.6s | 13.6s | 17 |
| 630s | 0.0s | 0.0s | 23 |
| 660s | 0.0s | 0.0s | 29 |
| 690s | 0.0s | 0.0s | 32 |
| 720s | 0.0s | 0.0s | 34 |
| 750s | 0.0s | 0.0s | 34 |
| 780s | 0.0s | 0.0s | 34 |
| 810s | 144.8s | 164.2s | 32 |
| 840s | 290.8s | 307.9s | 22 |
| 870s | 260.8s | 282.4s | 14 |
First seed in full
Every submission is public. That is the point — the interesting part is how somebody else solved it.
// LOOK — the classic lift algorithm, and the one your office probably runs.//// Every car is sweeping in one direction. It serves each stop ahead of it on that// leg, then turns round and sweeps back. The name is the important part: unlike// SCAN, which runs to the end of the shaft whether or not anybody is up there, LOOK// only goes as far as the furthest outstanding request before reversing.//// The rule that makes it work — and the one people miss — is that a hall call is// only answered on the leg going the caller's way. Stopping for a down-call on the// way up just opens the doors at somebody who does not want to get in.//// Note what you are not given: how many people are waiting anywhere, and how many// are in any car. A lit button is a lit button. Working around that is the job.const SECONDS_PER_FLOOR = 0.6const SECONDS_PER_STOP = 5const CROWDING_PENALTY = 6export default { setup(building) { this.top = building.floors - 1 this.heading = new Map() }, tick(world) { const cars = world.elevators.filter((car) => !car.outOfService) if (cars.length === 0) return // Hand each lit button to whichever car reaches it soonest on the sweep it is // already committed to. const work = new Map() for (const car of cars) work.set(car.id, []) for (const call of world.calls) { let best = null let bestCost = Infinity // If every car is over its cap the call still has to go somewhere, or in a // one-lift building nothing would ever be assigned and the car would sit // there while the lobby filled up. let fallback = null let fallbackCommitted = Infinity for (const car of cars) { // Do not keep piling work onto a car that already has plenty. Without a load // reading this is the only brake there is, and without a brake one car ends // up promising the whole building while the others sit idle. const committed = this.busy(car) + work.get(car.id).length if (committed < fallbackCommitted) { fallbackCommitted = committed fallback = car } if (committed >= car.capacity) continue const cost = this.reach(car.floor, this.heading.get(car.id), call.floor, call.direction) * SECONDS_PER_FLOOR + work.get(car.id).length * SECONDS_PER_STOP + committed * CROWDING_PENALTY if (cost < bestCost) { bestCost = cost best = car } } const chosen = best || fallback if (chosen) work.get(chosen.id).push(call) } for (const car of cars) this.drive(car, work.get(car.id), cars) }, // How busy a car looks. // // Nothing anywhere tells you how many people are in a lift — that is the point of // the game. The closest honest signal is how many different floors the people // inside have already asked for: eight people all going to the fifth floor look // like one stop, and they are. It is a lower bound, and it is enough to stop you // loading every call onto the same car. busy(car) { return car.pressed.length }, // How far this car has to travel, on its current sweep, to answer that call. reach(position, heading, floor, wants) { if (!heading) return Math.abs(position - floor) if (heading === 'up') { if (floor >= position && wants === 'up') return floor - position // We carry on up, turn round at the top of the run, and collect them coming back. if (wants === 'down') return this.top - position + (this.top - floor) // Behind us and going our way: they wait for the whole round trip. return this.top - position + this.top + floor } if (floor <= position && wants === 'down') return position - floor if (wants === 'up') return position + floor return position + this.top + (this.top - floor) }, drive(car, calls, cars) { const here = car.atFloor const loading = car.doors !== 'closed' // Two kinds of stop: somebody inside wants off here, or somebody outside wants // on. Only the second kind cares which way the car is going. const stops = [] for (const floor of car.pressed) stops.push({ floor, kind: 'car' }) for (const call of calls) stops.push({ floor: call.floor, kind: call.direction }) // Never re-queue the floor the car is standing at with its doors open. It would // shut them and immediately open them again for nobody. const pending = stops.filter((s) => !(loading && here !== null && s.floor === here)) if (pending.length === 0) { this.heading.delete(car.id) car.indicate(null) // An idle car is worth more at the lobby than wherever it happened to finish. // One car covers it; the rest stay put rather than all stampeding downstairs. const lobbyCovered = cars.some( (other) => other.id !== car.id && (other.floor < 0.5 || other.queue[0] === 0), ) if (!lobbyCovered && car.floor > 0.5) car.goTo(0) return } let start = this.heading.get(car.id) if (!start) { let nearest = Infinity for (const stop of pending) { const distance = Math.abs(stop.floor - car.floor) if (distance < nearest) { nearest = distance start = stop.floor >= car.floor ? 'up' : 'down' } } } const plan = this.sweep(car.floor, start, pending) this.heading.set(car.id, plan.heading) car.setQueue(plan.queue) car.indicate(this.lantern(here, loading, plan, pending)) }, // Order the stops into legs: everything this way, then everything back, and so on. sweep(position, start, stops) { const left = stops.slice() const order = [] let cursor = position let heading = start let leading = null for (let leg = 0; leg < 4 && left.length > 0; leg++) { // This is the LOOK part: turn round at the furthest request, not at the end // of the shaft. const floors = left.map((s) => s.floor) const reach = heading === 'up' ? Math.max(cursor, ...floors) : Math.min(cursor, ...floors) const taken = left.filter((s) => { const wanted = s.kind === 'car' || s.kind === heading const onTheWay = heading === 'up' ? s.floor >= cursor - 0.001 : s.floor <= cursor + 0.001 return wanted && onTheWay }) taken.sort((a, b) => (heading === 'up' ? a.floor - b.floor : b.floor - a.floor)) if (taken.length > 0 && !leading) leading = heading for (const stop of taken) { left.splice(left.indexOf(stop), 1) if (!order.includes(stop.floor)) order.push(stop.floor) } cursor = reach heading = heading === 'up' ? 'down' : 'up' } for (const stop of left) if (!order.includes(stop.floor)) order.push(stop.floor) return { queue: order, heading: leading || start } }, // Which way to point the lantern: the direction the car will travel *from the stop // it is about to make*, not the direction it is travelling now. lantern(here, loading, plan, stops) { const anchor = loading && here !== null ? here : plan.queue[0] if (anchor === undefined) return plan.heading // There is no down from the ground, and no up from the top. if (anchor <= 0) return 'up' if (anchor >= this.top) return 'down' const next = loading && here !== null ? plan.queue[0] : plan.queue[1] const onward = next !== undefined && next !== anchor ? (next > anchor ? 'up' : 'down') : null // If the car is going there to answer a hall call, it has to show that call's // direction — otherwise the people it came for will not get in, the call stays // lit, and it will keep coming back and keep not collecting them. const wanted = new Set() for (const stop of stops) { if (stop.floor === anchor && stop.kind !== 'car') wanted.add(stop.kind) } if (wanted.size === 1) return [...wanted][0] return onward || plan.heading },}