Orbital Snaffles

ERMEGHERED ERBETEL SNEFFLES!!!

by SwagColoredKitteh

HTML

<div id="main">
  <input id="steps" type="text" value="1" />
  <input id="step" type="button" value="Step" />
  <input id="next" type="button" value="Next Collision" />
  <input id="reset" type="button" value="Reset" />
  <input id="auto-step" type="button" value="Play" />
  <canvas id="canvas" width="600"></canvas>
</div>

CSS

#main {
  margin: 20px auto;
  width: 600px;
}

TypeScript

//
// DOM stuff
//
type Context = CanvasRenderingContext2D;
type Canvas = HTMLCanvasElement;

const q = document.querySelector.bind(document);

const doc = {
  canvas: q("#canvas"),
  step: q("#step"),
  steps: q("#steps"),
  next: q("#next"),
  reset: q("#reset"),
  autoStep: q("#auto-step")
};

//
// Constants
//
const MINIMUM_IMPULSE: number = 10;

const BORDER_LEFT_X: number = 0;
const BORDER_RIGHT_X: number = 24000;
const BORDER_TOP_Y: number = 0;
const BORDER_BOTTOM_Y: number = 16000;

const CENTER_MASS: number = 2e9;
const CENTER_RADIUS: number = 1500;

const GRAVITATIONAL_CONSTANT: number = 6.674e-1;

const SIM_SPEED: number = 4;

const SNAFFLE_SPAWN_WIDTH: number = 20000;
const SNAFFLE_SPAWN_HEIGHT: number = 9000;

const MIDDLE_X: number = (BORDER_LEFT_X + BORDER_RIGHT_X) / 2;
const MIDDLE_Y: number = (BORDER_BOTTOM_Y + BORDER_TOP_Y) / 2;
const MIDDLE_RADIUS: number = 3000;

const LEFT_WIZARD_X: number = BORDER_LEFT_X + 1000;
const RIGHT_WIZARD_X: number = BORDER_RIGHT_X - 1000;
const WIZARD_Y: number = MIDDLE_Y;
const WIZARD_POS_RADIUS: number = 1500;

const GOAL_Y: number = MIDDLE_Y;
const GOAL_RADIUS: number = 2000;

const GAME_CANVAS_WIDTH: number = 600;
const GAME_CANVAS_HEIGHT: number = Math.ceil(GAME_CANVAS_WIDTH * (BORDER_BOTTOM_Y / BORDER_RIGHT_X));

doc.canvas.height = GAME_CANVAS_HEIGHT + 20;

const SCALE_FACTOR: number = (GAME_CANVAS_WIDTH - 20) / BORDER_RIGHT_X;

//
// Support
//
class Vec2 {
  public x: number;
  public y: number;
  
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
    Object.freeze(this);
  }

  static fromAngle(angle: number, len: number): Vec2 {
    return new Vec2(Math.cos(angle) * len, Math.sin(angle) * len);
  }
  
  equals(other: Vec2): boolean {
    return this.x == other.x && this.y == other.y;
  }
  
  add(other: Vec2): Vec2 {
    return new Vec2(this.x + other.x, this.y + other.y);
  }
  
  sub(other: Vec2): Vec2 {
    return new Vec2(this.x - other.x, this.y - other.y);
  }
  
  lenSq():...