Click to switch which train is faster. Don't let them collide! Smaller window = bigger challenge.
for(i in c){c[i[0]+(i[6]||i.length)]=c[i]};M=Math;d=[];r=[];g="#9991#3601#9601#f0f1#0f0".split(1);T=Z=1;p=4;q=9;n=33;W=a.width;H=a.height;P=6.28;B=(W>H?H:W)*4/9;c.font=B/2+"px Arial";for(i=0;i<n;)d[i]={s:i<5?0:9,d:20*i},r[i++]={x:W/2+M.sin(i/(n/4)*P)*B,y:H/2+M.cos(i/(n/3)*P)*B};for(i=0;i<n;)R=r[i++],w=r[i%n],u=w.x-R.x,v=w.y-R.y,R.d=M.sqrt(u*u+v*v);function h(k,l){c.ba();c.strokeStyle=g[k];c.lineWidth=l;c.m6(r[i].x,r[i].y);v=r[(i+1)%n];c.l6(v.x,v.y);c.s6()}function f(e){if(e)Q=q,q=p,p=Q,T-=9;c.fillStyle=g[Z];c.fc(0,0,W,H);c.lineCap="butt";for(i=0;i<n;i++)h(2,9),c.se([]),h(0,7),h(1,5),c.se([2,4]),h(2,3);c.sT(T+=Z,9,H-9);for(c.lineWidth=i=9;--i>=0;){z=d[i];z.d+=i>4?q:p;u=z.s;v=r[u];while(z.d>v.d)z.d-=v.d,u=z.s=(z.s+1)%n,v=r[u];w=r[(u+1)%n];dx=(w.x-v.x)/v.d;dy=(w.y-v.y)/v.d;z.x=v.x+z.d*dx;z.y=v.y+z.d*dy;c.ba();c.strokeStyle=g[i<5?3:4];c.lineCap="round";c.m6(z.x,z.y);c.l6(z.x-dx*9,z.y-dy*9);c.s6();if(i==4||i==8)for(J=0;J<9;J++)if(J^i){K=z.x-d[J].x;L=z.y-d[J].y;if(L*L+K*K<99)Z=p=q=0}}}setInterval(f,n);onmousedown=f
Zm9yKGkgaW4gYyl7Y1tpWzBdKyhpWzZdfHxpLmxlbmd0aCldPWNbaV19O009TWF0aDtkPVtdO3I9W107Zz0iIzk5OTEjMzYwMSM5NjAxI2YwZjEjMGYwIi5zcGxpdCgxKTtUPVo9MTtwPTQ7cT05O249MzM7Vz1hLndpZHRoO0g9YS5oZWlnaHQ7UD02LjI4O0I9KFc+SD9IOlcpKjQvOTtjLmZvbnQ9Qi8yKyJweCBBcmlhbCI7Zm9yKGk9MDtpPG47KWRbaV09e3M6aTw1PzA6OSxkOjIwKml9LHJbaSsrXT17eDpXLzIrTS5zaW4oaS8obi80KSpQKSpCLHk6SC8yK00uY29zKGkvKG4vMykqUCkqQn07Zm9yKGk9MDtpPG47KVI9cltpKytdLHc9cltpJW5dLHU9dy54LVIueCx2PXcueS1SLnksUi5kPU0uc3FydCh1KnUrdip2KTtmdW5jdGlvbiBoKGssbCl7Yy5iYSgpO2Muc3Ryb2tlU3R5bGU9Z1trXTtjLmxpbmVXaWR0aD1sO2MubTYocltpXS54LHJbaV0ueSk7dj1yWyhpKzEpJW5dO2MubDYodi54LHYueSk7Yy5zNigpfWZ1bmN0aW9uIGYoZSl7aWYoZSlRPXEscT1wLHA9USxULT05O2MuZmlsbFN0eWxlPWdbWl07Yy5mYygwLDAsVyxIKTtjLmxpbmVDYXA9ImJ1dHQiO2ZvcihpPTA7aTxuO2krKyloKDIsOSksYy5zZShbXSksaCgwLDcpLGgoMSw1KSxjLnNlKFsyLDRdKSxoKDIsMyk7Yy5zVChUKz1aLDksSC05KTtmb3IoYy5saW5lV2lkdGg9aT05Oy0taT49MDspe3o9ZFtpXTt6LmQrPWk+ND9xOnA7dT16LnM7dj1yW3VdO3doaWxlKHouZD52LmQpei5kLT12LmQsdT16LnM9KHoucysxKSVuLHY9clt1XTt3PXJbKHUrMSklbl07ZHg9KHcueC12LngpL3YuZDtkeT0ody55LXYueSkvdi5kO3oueD12Lngrei5kKmR4O3oueT12Lnkrei5kKmR5O2MuYmEoKTtjLnN0cm9rZVN0eWxlPWdbaTw1PzM6NF07Yy5saW5lQ2FwPSJyb3VuZCI7Yy5tNih6Lngsei55KTtjLmw2KHoueC1keCo5LHoueS1keSo5KTtjLnM2KCk7aWYoaT09NHx8aT09OClmb3IoSj0wO0o8OTtKKyspaWYoSl5pKXtLPXoueC1kW0pdLng7TD16LnktZFtKXS55O2lmKEwqTCtLKks8OTkpWj1wPXE9MH19fXNldEludGVydmFsKGYsbik7b25tb3VzZWRvd249Zg==
//
// CAN'T STOP THE HYPE TRAIN
// by
// Dan Efran
//
// a game in 1024 bytes of JavaScript, for JS1k 2015
// http://js1k.com/2015-hypetrain/
//
// (This formatted, commented version is longer. For the 1k version,
// only comments and whitespace are removed from this version.)
//
// More cool stuff to brighten your day:
//
// http://www.dan-efran.com
//
// FULLY COMMENTED VERSION...
// Create abbreviations for wordy members of canvas context.
// e.g. m6 for moveTo
for(i in c){c[i[0]+(i[6]||i.length)]=c[i]};
// Abbreviate Math, too - just the name, not its members.
M=Math;
// d: an array of train cars
d=[];
// r: an array of rail segments
r=[];
// g: an array of colors.
// unpacked from a string to save the space of a few punctuation marks.
g="#9991#3601#9601#f0f1#0f0".split(1);
// T: elapsed tick count (minus penalties); the game score
// Z: game running flag; 0 if trains have crashed, 1 until then
// p = first train's current speed
// q = second train's current speed
// n = number of track segments; ALSO, tick length for setInterval: 33.
T=Z=1;
p=4;
q=9;
n=33;
// W: canvas width
// H: canvas height
W=a.width;
H=a.height;
// P: a two-pi-esque constant for drawing spirographic patterns
P=6.28;
// B: a sizeable fraction of the canvas's smaller dimension
B=(W>H?H:W)*4/9;
// A big font size to display the score.
c.font=B/2+"px Arial";
// Initialize the data arrays: cars and rails
for(i=0;i<n;)
// Train cars
// (created for every n, but cars for i>9 will not be used)
// s: starting rail segment; d:starting distance along it
// (this initial d value sets the trains' car-to-car spacing)
d[i]={s:i<5?0:9,d:20*i},
// Rails (all n)
// We draw a scribble of sine curves at weird frequencies,
// hand-tuned to be a moderately complex maze of crossing paths
// with a minimum of code.
// notice that the map is scaled to the window, but the trains
// are not. So the challenge level varies by window size.
// It's a feature! Game too easy? Make your window smaller.
// first we store the starting x,y of the line segment...
r[i++]={x:W/2+M.sin(i/(n/4)*P)*B,y:H/2+M.cos(i/(n/3)*P)*B};
// ...then we go back through the rails in a second loop, now that
// they all exist; here we calculate and save each one's length d
// (distance from the segment's x,y to the next segment's x,y)
// (wrapping around from the last one to the first again, of course.)
for(i=0;i<n;)
R=r[i++],
w=r[i%n],
u=w.x-R.x,
v=w.y-R.y,
R.d=M.sqrt(u*u+v*v);
// Draw a line along a rail segment
// color and line width are specified as parameters, since we'll be
// drawing several lines on top of each other for each rail segment
function h(k,l){
c.ba();
c.strokeStyle=g[k];
c.lineWidth=l;
c.m6(r[i].x,r[i].y);
v=r[(i+1)%n];
c.l6(v.x,v.y);
c.s6()
}
// Main tick function; also mouse event handler
function f(e){
// If f is called with an event e, we handle a mouse click:
// swap the train speeds, and deduct a few points from the score.
if(e)Q=q,q=p,p=Q,T-=9;
// We don't bother to skip the rest of f when handling a click,
// so the game advances an extra frame when you click. Oh well.
// Erase the background (green during the game, gray after a crash)
c.fillStyle=g[Z];
c.fc(0,0,W,H);
// Draw tracks:
// A. a thick brown line* for the ties sticking out beyond the rails
// B. a thinner (but still thick) line for the rails
// C. a thin green line between the rails to show grass between them.
// D. a thin dotted brown line for the ties showing between the rails.
// * then the dotted-line style falls back around the loop to A.,
// so the thick brown line is dotted too. (The logic is imperfect
// but the results look fine.)
c.lineCap="butt";
for(i=0;i<n;i++)
h(2,9),
c.se([]),
h(0,7),
h(1,5),
c.se([2,4]),
h(2,3);
// Draw the score; if the trains haven't crashed, increment it.
c.sT(T+=Z,9,H-9);
// Handle the train cars
for(c.lineWidth=i=9;--i>=0;){
// Move car along track.
// z: this car
z=d[i];
// Advance its distance-along-the-track-segment by its speed
z.d+=i>4?q:p;
// u: the car's current track segment index
// v: that track segment
u=z.s;
v=r[u];
// if the car's distance along the segment exceeds the
// segment's length, subtract the segment's length,
// move the car to the next segment, and update u and v.
// repeat this until the car is actually within its segment
while(z.d>v.d)
z.d-=v.d,
u=z.s=(z.s+1)%n,
v=r[u];
// w: the next track segment
w=r[(u+1)%n];
// dx, dy: the unit vector pointing from v toward w.
dx=(w.x-v.x)/v.d;
dy=(w.y-v.y)/v.d;
// Set the car's position: Its segment's start position,
// plus that unit vector times the car's distance along the segment.
z.x=v.x+z.d*dx;
z.y=v.y+z.d*dy;
// Draw the car
// Color is per train (split between trains is hardcoded).
c.ba();
c.strokeStyle=g[i<5?3:4];
c.lineCap="round";
c.m6(z.x,z.y);
// Cars are oriented back along the rail segment's unit vector.
c.l6(z.x-dx*9,z.y-dy*9);
c.s6();
// Collision test:
// if the current car is the head of a train...
if(i==4||i==8)
// ...test against all the other cars (but not itself)...
for(J=0;J<9;J++)
if(J^i){
// ...comparing the distance-squared between them
// to a collision radius-squared.
K=z.x-d[J].x;
L=z.y-d[J].y;
// If the i car did hit the J car,
// stop both trains and declare the game over.
if(L*L+K*K<99)Z=p=q=0
}
}
}
// Set the main function to run once per tick, and once per click.
setInterval(f,n);
onmousedown=f
// The end. That's 1024 bytes of source code, once you strip out
// these comments and all unnecessary whitespace.
// (A few spaces are needed, such as those around "in".)