Lander Source Code

<!DOCTYPE html>
<html>
<head>
<title>Lander</title>
</head>
<body style="margin:0px; overflow:hidden;" onload="init()">
<canvas id="canvas"></canvas>
<script>

//Set up the canvas to be drawn to with the ctx context
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
var cw = canvas.width = window.innerWidth;
var ch = canvas.height = window.innerHeight;

//Section for global varibles, this includes loading the outside images
var surfacePts, landingSpot, lander, orbiter, fuel, message;
var landerPic = new Image();
landerPic.src = "lander.png";
var orbiterPic = new Image();
orbiterPic.src = "orbiter.png";
var key = {up:false,down:false,left:false,right:false}

//Init is called one time at the very beginning when the body loads
function init() {
  reset();
  message = ["Welcome to Lander","Use WASD or Arrow Keys to Control Lander",
    "Press a Key to Start"];
  draw();
  animate();
}

//Reset is for code that changes variables back to original values
function reset() {
  landingSpot=rn(cw/3,cw*2/3);
  lander = {x:-100,y:-100,vx:-1,vy:0,dock:true,land:false};
  orbiter = {x:cw,y:0,vx:-1,vy:0};
  surfacePts = [];
  surfaceGen(); 
  fuel = 6000;
}

//This function generates a rough looking surface and stores the info
//into surfacePts.
function surfaceGen() {
  var y=ch-50;
  for (var i=0; i<=cw; i++) {
    var change = rn(-2,2);
    if (Math.abs(landingSpot-i) < 50) change = 0;
    if (y < ch-300) change = 1;
    if (y > ch-50) change = -1;
    y += change;
    surfacePts.push(y);
  }
}

//Animate contains code/functions that need to be updated each frame
function animate() {
  window.requestAnimationFrame(animate);
  detectLanding();
  detectDocking();
  applyVelocity();
  if (fuel > 0) applyKeys();
  draw();
}

//This function moves the lander and orbiter
//If the lander is docked then the lander's position
//is just offset from the orbiter. The position is
//changed by each objects velocity. Gravity is also
//incorporated here. Finally, if the objects are too
//far off-screen, they are moved to the other side.
function applyVelocity() {
  if (lander.dock) {
    lander.x = orbiter.x-23;
    lander.y = orbiter.y+140;
  } else {
    lander.vy += .005;
    lander.x += lander.vx;
    lander.y += lander.vy;
  }
  orbiter.x += orbiter.vx;
  orbiter.y += orbiter.vy;
  if (orbiter.x < -200) orbiter.x = cw+200;
  if (lander.x < -200) lander.x = cw+200;
  if (lander.x > cw+200) lander.x = -200;
}

//ApplyKeys will change the velocities and fuel usage
//if the appropriate keys are being held down.
function applyKeys() {
  if (key.up) {
    lander.vy-=.01;
    fuel -= 5;
  }
  if (key.up || key.left || key.right) fuel -= 1;
  if (key.left) lander.vx-=.01;
  if (key.right) lander.vx+=.01;
  if (key.down) {
    if (lander.dock) {
      lander.vx = orbiter.vx; 
      lander.dock = false;
    }
  }
}

//This function detects any interaction with the surface.
//If the lander's feet go below the surface then the lander
//will be brought back above the surface. If the vertical
//velocity is too great the lander will crash.
//If the lander is close to the landing spot then landing is
//successful. In order to settle the lander the velocity will
//bounce with half the velocity, unless there is thrust.
function detectLanding() {
  var x = Math.floor(lander.x);
  if (x < 0) x = 0;
  if (x >= cw-90) x = cw-91;
  var left = (lander.y+70)- surfacePts[x];
  var right = (lander.y+70) - surfacePts[x+90]; 
  if ( left > 0 || right > 0) {
    if (left > right) lander.y -= left;
    else              lander.y -= right;
    if (lander.vy > .5) {reset(); message=["You Crashed!"];}
    if (Math.abs(lander.x+50 - landingSpot) < 10) lander.land = true;
    if (!key.up) lander.vy *= -.5;
    lander.vx *=.5;
  }
}

//This function will detect when the lander docks with the
//orbiter. If the difference in velocity is too great there
//will be a crash.
function detectDocking() {
  var d = dist({x:lander.x+23,y:lander.y-140}, orbiter);
  if (d < 10 && (lander.land)) { 
    if ((Math.abs(orbiter.vx - lander.vx) < .5) &&
        (Math.abs(orbiter.vy - lander.vy) < .5)) {
      if (lander.land) {
        lander.dock = true;
        message = ["Mission Accomplished!"];
      } else {reset(); message=["You Crashed into the Orbiter!"];} 
    }
  }
}

//Draw contains drawing code/functions and is called each frame
function draw() {
  ctx.fillStyle="black";
  ctx.fillRect(0,0,cw,ch);
  if (key.up && fuel > 0) drawThrust();
  drawSurface();
  ctx.drawImage(landerPic,lander.x,lander.y,90,80);
  ctx.drawImage(orbiterPic,orbiter.x,orbiter.y);
  drawLights();
  drawTelemetry();
  if (message.length > 0) drawMessage();
}

//This function draws the surface by looping through surfacePts.
//It draws a line at each point and there is a point for each pixel.
//That means each line segment is only 1 pixel. This could be messed with.
function drawSurface() {
  ctx.fillStyle="#444444";
  ctx.beginPath();
  ctx.moveTo(0,cw-50);
  for(var i=0; i<cw; i++) {
    ctx.lineTo(i,surfacePts[i]);
  }
  ctx.lineTo(cw,ch);
  ctx.lineTo(0,ch);
  ctx.lineTo(0,50);
  ctx.fill();
}

//Draws some pretty blue flames. Most of the code here is setting up the gradient
function drawThrust() {
  var grd = ctx.createLinearGradient(0,lander.y+60,0,lander.y+85);
  grd.addColorStop(0,"white");
  grd.addColorStop(.25,"blue");
  grd.addColorStop(.5,"white");
  grd.addColorStop(.75,"blue");
  grd.addColorStop(1,"white");
  ctx.fillStyle=grd;
  drawCone(lander.x+30,lander.y+50);
  drawCone(lander.x+60,lander.y+50);
}

//This is basically a helper function for drawThrust
function drawCone(x,y) {
  ctx.beginPath();
  ctx.moveTo(x-5,y);
  ctx.lineTo(x+5,y);
  ctx.lineTo(x,y+40);
  ctx.fill();
}

//Draws lights to indicate successful landing and docking
function drawLights() {
  var c;
  if (lander.land) c = "lime";
  else             c = "red";
  circle(landingSpot-50,surfacePts[landingSpot],3,c);
  circle(landingSpot+50,surfacePts[landingSpot],3,c);
  
  if (lander.dock) c = "lime";
  else             c = "red";
  circle(orbiter.x+24,orbiter.y+78,2,c);
  circle(orbiter.x+24,orbiter.y+101,2,c);
  circle(orbiter.x+24,orbiter.y+120,2,c);
}

//Draws fuel and VSI indicators. The color assignment is a special
//conditional assignment that allows assignment with a built in
//if statement. The colors are used to show danger.
function drawTelemetry() {
  ctx.font="30px Arial";
  ctx.fillStyle=(fuel < 1000)? "yellow":"lime";
  if (fuel < 100) ctx.fillStyle="red"; 
  ctx.fillText("FUEL",cw-100,ch/2-20);
  ctx.fillText(fuel, cw-100,ch/2+20);
  var vsi=Math.floor(lander.vy*-10)
  ctx.fillStyle=(Math.abs(vsi) > 3)? "yellow":"lime";
  if (Math.abs(vsi) > 4) ctx.fillStyle = "red";
  ctx.fillText("VSI",10,ch/2-20);
  ctx.fillText(vsi,10,ch/2+20);
}

//Displays any messages for the user. Messages should be an array
//This function will only get called if there is something in the
//array. This function will display the messages centered in middle
function drawMessage() {
  ctx.font="50px Arial";
  ctx.fillStyle="white";
  for (var i=0, len=message.length; i<len; i++) {
    var width = ctx.measureText(message[i]).width;
    ctx.fillText(message[i], cw/2-width/2, ch/2-len*40+i*80);
  }
}

//Function to draw a circle
function circle(x,y,r,c) {
  ctx.beginPath();
  ctx.arc(x,y,r,0,Math.PI*2);
  ctx.fillStyle=c;
  ctx.fill();
}


//Mouse click event listener and function to get x and y position
//This also clears out messages if there are any and resets after a
//successful mission
window.addEventListener("keydown", function(event) {keyDown(event);});
function keyDown(event) {
  keyAction(event.keyCode,true);
  if (message.length > 0) {
    if (lander.dock && lander.land) reset(); 
    message = [];
  }
}

//This listens for keyup and sends the event to keyAction
window.addEventListener("keyup", function(event) {keyUp(event);});
function keyUp(event) {
  keyAction(event.keyCode,false);
}

//Keyaction takes the key up and down signals from the listeners
//and applies them to the appropriate key boolean values
function keyAction(k,action) {
  if (k == 40 || k == 83) key.down = action;
  if (k == 37 || k == 65) key.left = action;
  if (k == 39 || k == 68) key.right = action;
  if (k == 38 || k == 87) key.up = action; 
}

//Random function that returns values between and including min/max
function rn(min,max) {
  return Math.floor( Math.random()*((max+1)-min)+min);
}

//Distance function takes 2 points and returns the distance
function dist(p1,p2) {
	return Math.sqrt(Math.pow(p1.x-p2.x,2)+Math.pow(p1.y-p2.y,2));
}

</script>
</body>
</html>