magwo / elevatorsaga

The elevator programming game!

Home Page:http://play.elevatorsaga.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TypeScript for the API

Josef37 opened this issue · comments

I wrote type definitions for the API, but didn't know where to make them accessible...

So for anyone searching online, here you go:
https://gist.github.com/Josef37/e075b6a005a47d146c7e7ab9ed7ae893

Here's the full file in case the link doesn't work for any reason...
/**
 * Main interface.
 *
 * Can be used like this:
 * ```
 * const solution: Solution = {
 *   init: (elevators, floors) => {
 *     // Here be code...
 *   },
 *   update: (dt, elevators, floors) => {
 *     // Here be more code...
 *   },
 * };
 * ```
 * Make sure to only copy starting at `{`.
 */
interface Solution {
  init: (elevators: Elevator[], floors: Floor[]) => void;
  update: (dt: number, elevators: Elevator[], floors: Floor[]) => void;
}

interface Elevator {
  /**
   * Queue the elevator to go to specified floor number.
   * If you specify true as second argument, the elevator will go to that floor directly, and then go to any other queued floors.
   */
  goToFloor(floor: number, forceNow?: boolean): void;

  /**
   * Clear the destination queue and stop the elevator if it is moving.
   * Note that you normally don't need to stop elevators - it is intended for advanced solutions with in-transit rescheduling logic.
   * Also, note that the elevator will probably not stop at a floor, so passengers will not get out.
   */
  stop(): void;

  /**
   * Gets the floor number that the elevator currently is on.
   */
  currentFloor(): number;

  /**
   * Gets or sets the going up indicator, which will affect passenger behaviour when stopping at floors.
   */
  goingUpIndicator(): boolean;
  goingUpIndicator(state: boolean): Elevator;

  /**
   * Gets or sets the going down indicator, which will affect passenger behaviour when stopping at floors.
   */
  goingDownIndicator(): boolean;
  goingDownIndicator(state: boolean): Elevator;

  /**
   * Gets the maximum number of passengers that can occupy the elevator at the same time.
   */
  maxPassengerCount(): number;

  /**
   * Gets the load factor of the elevator. 0 means empty, 1 means full. Varies with passenger weights, which vary - not an exact measure.
   */
  loadFactor(): number;

  /**
   * Gets the direction the elevator is currently going to move toward. Can be \"up\", \"down\" or \"stopped\".
   */
  destinationDirection(): "up" | "down" | "stopped";

  /**
   * The current destination queue, meaning the floor numbers the elevator is scheduled to go to.
   * Can be modified and emptied if desired. Note that you need to call checkDestinationQueue() for the change to take effect immediately.
   */
  destinationQueue: number[];

  /**
   * Checks the destination queue for any new destinations to go to.
   * Note that you only need to call this if you modify the destination queue explicitly.
   */
  checkDestinationQueue(): void;

  /**
   * Gets the currently pressed floor numbers as an array.
   */
  getPressedFloors(): number[];

  /**
   * Triggered when the elevator has completed all its tasks and is not doing anything.
   */
  on(event: "idle", callback: () => void): Elevator;
  /**
   * Triggered when a passenger has pressed a button inside the elevator.
   */
  on(
    event: "floor_button_pressed",
    callback: (floorNum: number) => void
  ): Elevator;
  /**
   * Triggered slightly before the elevator will pass a floor. A good time to decide whether to stop at that floor.
   * Note that this event is not triggered for the destination floor. Direction is either "up" or "down".
   */
  on(
    event: "passing_floor",
    callback: (floorNum: number, direction: "up" | "down") => void
  ): Elevator;
  /**
   * Triggered when the elevator has arrived at a floor.
   */
  on(event: "stopped_at_floor", callback: (floorNum: number) => void): Elevator;
}

interface Floor {
  /**
   * Gets the floor number of the floor object.
   */
  floorNum(): number;

  /**
   * Triggered when someone has pressed the up button at a floor.
   * Note that passengers will press the button again if they fail to enter an elevator.
   */
  on(event: "up_button_pressed", callback: () => void): Floor;

  /**
   * Triggered when someone has pressed the down button at a floor.
   * Note that passengers will press the button again if they fail to enter an elevator.
   */
  on(event: "down_button_pressed", callback: () => void): Floor;
}

I worked on this as well:

elevator-saga.ts
// Derived from documentation:
// Site: https://play.elevatorsaga.com/documentation.html
// Repo: https://github.com/magwo/elevatorsaga/blob/master/documentation.html

type Direction = 'down' | 'up';

type ElevatorIdleEventCallback = () => void;
type ElevatorFloorButtonPressedCallback = (floorNum: number) => void;

type ElevatorPassingFloorCallback = (
  floorNum: number,
  direction: Direction,
) => void;

type ElevatorStoppedAtFloorCallback = (floorNum: number) => void;

type FloorUpButtonPressedCallback = () => void;
type FloorDownButtonPressedCallback = () => void;

type Elevator = {
  /**
   * Queue the elevator to go to specified floor number. If you specify true as
   * second argument, the elevator will go to that floor directly, and then go
   * to any other queued floors.
   * 
   * ```ts
   * elevator.goToFloor(3); // Do it after anything else
   * elevator.goToFloor(2, true); // Do it before anything else
   * ```
   */
  goToFloor (floorNum: number, directly?: boolean): void;

  /**
   * Clear the destination queue and stop the elevator if it is moving. Note
   * that you normally don't need to stop elevators - it is intended for
   * advanced solutions with in-transit rescheduling logic. Also, note that the
   * elevator will probably not stop at a floor, so passengers will not get out.
   * 
   * ```ts
   * elevator.stop();
   * ```
   */
  stop (): void;

  /**
   * Gets the floor number that the elevator currently is on.
   * 
   * ```ts
   * if(elevator.currentFloor() === 0) {
   *     // Do something special?
   * }
   * ```
   */
  currentFloor (): number;

  /**
   * Gets or sets the going up indicator, which will affect passenger behaviour
   * when stopping at floors.
   * 
   * ```ts
   * if(elevator.goingUpIndicator()) {
   *     elevator.goingDownIndicator(false);
   * }
   * ```
   */
  goingUpIndicator (): boolean;
  goingUpIndicator (state: boolean): void;

  /**
   * Gets or sets the going down indicator, which will affect passenger
   * behaviour when stopping at floors.
   * 
   * ```ts
   * if(elevator.goingDownIndicator()) {
   *     elevator.goingUpIndicator(false);
   * }
   * ```
   */
  goingDownIndicator (): boolean;
  goingDownIndicator (state: boolean): void;

  /**
   * Gets the maximum number of passengers that can occupy the elevator at the
   * same time.
   * 
   * ```ts
   * if(elevator.maxPassengerCount() > 5) {
   *     // Use this elevator for something special, because it's big
   * }
   * ```
   */
  maxPassengerCount (): number;

  /**
   * Gets the load factor of the elevator. 0 means empty, 1 means full.
   * Varies with passenger weights, which vary - not an exact measure.
   * 
   * ```ts
   * if(elevator.loadFactor() < 0.4) {
   *     // Maybe use this elevator, since it's not full yet?
   * }
   * ```
   */
  loadFactor (): number;

  /**
   * Gets the direction the elevator is currently going to move toward.
   * Can be "up", "down" or "stopped".
   */
  destinationDirection (): Direction | 'stopped';

  /**
   * The current destination queue, meaning the floor numbers the elevator
   * is scheduled to go to. Can be modified and emptied if desired. Note that
   * you need to call checkDestinationQueue() for the change to take effect
   * immediately.
   * 
   * ```ts
   * elevator.destinationQueue = [];
   * elevator.checkDestinationQueue();
   * ```
   */
  destinationQueue: number[];

  /**
   * Checks the destination queue for any new destinations to go to. Note that
   * you only need to call this if you modify the destination queue explicitly.
   * 
   * ```ts
   * elevator.checkDestinationQueue();
   * ```
   */
  checkDestinationQueue (): void;

  /**
   * Gets the currently pressed floor numbers as an array.
   * 
   * ```ts
   * if(elevator.getPressedFloors().length > 0) {
   *     // Maybe go to some chosen floor first?
   * }
   * ```
   */
  getPressedFloors (): number[];

  /**
   * Triggered when the elevator has completed all its tasks and is not doing
   * anything.
   * 
   * ```ts
   * elevator.on("idle", function() { ... });
   * ```
   */
  on (type: 'idle', callback: ElevatorIdleEventCallback): void;

  /**
   * Triggered when a passenger has pressed a button inside the elevator.
   * 
   * ```ts
   * elevator.on("floor_button_pressed", function(floorNum) {
   *     // Maybe tell the elevator to go to that floor?
   * })
   * ```
   */
  on (
    type: 'floor_button_pressed',
    callback: ElevatorFloorButtonPressedCallback,
  ): void;

  /**
   * Triggered slightly before the elevator will pass a floor. A good time to
   * decide whether to stop at that floor. Note that this event is not triggered
   * for the destination floor. Direction is either "up" or "down".
   * 
   * ```ts
   * elevator.on("passing_floor", function(floorNum, direction) { ... });
   * ```
   */
  on (type: 'passing_floor', callback: ElevatorPassingFloorCallback): void;

  /**
   * Triggered when the elevator has arrived at a floor.
   * 
   * ```ts
   * elevator.on("stopped_at_floor", function(floorNum) {
   *     // Maybe decide where to go next?
   * })
   * ```
   */
  on (type: 'stopped_at_floor', callback: ElevatorStoppedAtFloorCallback): void;
};

type Floor = {
  /**
   * Gets the floor number of the floor object.
   * 
   * ```ts
   * if(floor.floorNum() > 3) { ... }
   * ```
   */
  floorNum (): number;

  /**
   * Triggered when someone has pressed the up button at a floor. Note that
   * passengers will press the button again if they fail to enter an elevator.
   * 
   * ```ts
   * floor.on("up_button_pressed", function() {
   *     // Maybe tell an elevator to go to this floor?
   * })
   * ```
   */
  on (type: 'up_button_pressed', callback: FloorUpButtonPressedCallback): void;

  /**
   * Triggered when someone has pressed the down button at a floor. Note that
   * passengers will press the button again if they fail to enter an elevator.
   * 
   * ```ts
   * floor.on("down_button_pressed", function() {
   *     // Maybe tell an elevator to go to this floor?
   * })
   * ```
   */
  on (
    type: 'down_button_pressed',
    callback: FloorDownButtonPressedCallback,
  ): void;
};

type ProgramInitCallback = (
  elevators: readonly Elevator[],
  floors: readonly Floor[],
) => void;

/**
 * @param dt the number of game seconds that passed since the last time update
 * was called
 */
type ProgramUpdateCallback = (
  dt: number,
  elevators: readonly Elevator[],
  floors: readonly Floor[],
) => void;

type Program = {
  init: ProgramInitCallback;
  update: ProgramUpdateCallback;
};

((): Program => {
  class AssertionError extends Error {
    override name = 'AssertionError';
  }

  function assert (expr: unknown, msg?: string): asserts expr {
    if (!expr) throw new AssertionError(msg);
  }

  const init: ProgramInitCallback = (elevators, floors) => {
    const [elevator] = elevators;
    assert(elevator, 'First elevator not found');

    elevator.on('idle', () => {
      const floorNumbers = [0, 1, 2];
      for (const floorNum of floorNumbers) elevator.goToFloor(floorNum);
    });
  };

  const update: ProgramUpdateCallback = (dt, elevators, floors) => {
    // Nothing yet!
  }

  return {init, update};
})();

And here's the code above in the TypeScript Playground, so anyone can get started in the browser without installing any tooling — simply write your code in the playground, and then copy from the compiled JS in the right panel and paste into the game as you make progress (inline source maps are even included to improve the debugging experience). The code takes advantage of the change in #72, which enables a more flexible IIFE approach over just an object literal.

Thanks for providing the type declarations, @Josef37 !

I forked this repository and integrated your type declarations with Monaco Editor to enable in-game auto-complete (a.k.a. type-ahead, IntelliSense):

Screenshot:
auto-complete