/**
* ______ __ ______ __ __ ______ ______ ______ __ __ __ ______ __ __ __ __ _____
* /\ __ \/\ \ /\ ___\/\ "-./ \/\ __ \/\ == \/\__ _\/\ \/\ "-.\ \/\ == \/\ \/\ \/\ \/\ \/\ __-.
* \ \ \/\ \ \ \___\ \ __\\ \ \-./\ \ \ __ \ \ __<\/_/\ \/\ \ \ \ \-. \ \ __<\ \ \_\ \ \ \_\ \ \ \/\ \
* \ \_____\ \_____\ \_____\ \_\ \ \_\ \_\ \_\ \_\ \_\ \ \_\ \ \_\ \_\\"\_\ \_\ \_\ \_____\ \_____\ \____-
* \/_____/\/_____/\/_____/\/_/ \/_/\/_/\/_/\/_/ /_/ \/_/ \/_/\/_/ \/_/\/_/ /_/\/_____/\/_____/\/____/
*
*/
/**
* Funksjon som utvider en prototype og hvis det allerede finnes en verdi i prototypen så lages det en ny funksjon slik at begge funksjonene kalles
* @param {Array} sources - Prototype av objektet som er kilden
*/
Object.prototype.extend = function (sources) {
if (Array.isArray(sources)) {
for (var i = sources.length-1; i >= 0; i--) {
this.extend(sources[i]);
}
} else {
for (var k in sources) {
if (sources.hasOwnProperty(k)) {
var res = sources[k];
if(this[k]) {
res = (function (id) {
var old = sources[id],
neew = this[id];
return function () {
old.apply(this,arguments);
return neew.apply(this,arguments);
};
}).call(this,k);
}
this[k] = res;
}
}
}
};
/**
* Hjelpeobjekt med nyttige funksjoner
* @type {Object}
*/
var calc = {
clock: {
secondsToMMSS: function (secs) {
var res = "",
mins = secs/60;
secs = (mins - Math.floor(mins))*60;
if(mins < 10) res += '0'+Math.floor(mins).toString();
else res += Math.floor(mins).toString();
res += ':';
if(secs < 10) res += '0'+Math.floor(secs).toString();
else res += Math.floor(secs).toString();
return res;
}
},
geometry: {
/**
* Tar først inn cordinatene til 2 punkter og så inn en x-verdi som man ønsker å finne y verdien til
* @param {Number} ax - Første punkt x-verdi
* @param {Number} ay - Første punkt y-verdi
* @param {Number} bx - Andre punkt x-verdi
* @param {Number} by - Andre punkt y-verdi
* @param {Number} x - Verdien man ønsker å finne y verdien til
* @returns {Number} y-verdien av x-verdien man ville finne
*/
interpolation: function(ax, ay, bx, by, x){
return (((x - ax)*(by - ay))/(bx - ax)) + ay;
},
distance: function (pt1, pt2) {
return Math.sqrt((pt1.x - pt2.x)*(pt1.x - pt2.x)+(pt1.y - pt2.y)*(pt1.y - pt2.y));
},
median: function (pt1, pt2) {
return { x: (pt1.x + pt2.x) / 2, y: (pt1.y + pt2.y) / 2 };
},
gradient: function (pt1, pt2) {
return (pt2.y - pt1.y)/(pt2.x - pt1.x);
},
point: {
draw: function (pt,ctx,size,color) {
var s = size || 4;
ctx.save();
ctx.strokeStyle = color || "white";
ctx.translate(pt.x,pt.y);
ctx.beginPath();
ctx.moveTo(-s,-s);
ctx.lineTo(s,s);
ctx.moveTo(-s,s);
ctx.lineTo(s,-s);
ctx.stroke();
ctx.restore();
}
}
},
color: {
random: function () {
var rand_hex = '#' + ((1-Math.random()) * 0xFFFFFF << 0).toString(16);
return (calc.color.validHEX(rand_hex) ? rand_hex : calc.color.random());
},
validHEX: function (hex) {
return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
}
},
input: {
mouse: {
getPos: function (e, canvas) {
var rect = canvas.getBoundingClientRect();
return {
x: (e.clientX - rect.left) / (rect.right - rect.left) * canvas.width,
y: (e.clientY - rect.top) / (rect.bottom - rect.top) * canvas.height
};
}
},
file: {
json: function(file, callback) {
var rawFile = new XMLHttpRequest();
rawFile.overrideMimeType("application/json");
rawFile.open("GET", file, true);
rawFile.onreadystatechange = function() {
if (rawFile.readyState === 4 && rawFile.status == "200") {
callback(rawFile.responseText);
}
};
rawFile.send(null);
}
}
}
};
/**
* Forteller om man debugger eller ikke, er nyttig for hjelpelinjer under debugging
* @type {Boolean}
*/
var DEBUG = false;
/**
* Alle objekter som skal kunne tegnes på canvas skal arve egenskapene til denne klassen, men denne klassen skal ikke konstruere objekter alene.
* @param {Number} x - Global x-posisjon
* @param {Number} y - Global y-posisjon
* @constructor
*/
function DrawableObject(x, y) {
this.global = { x: x || 0, y: y || 0 };
this.color = {
stroke: "white",
fill: "black"
};
this.local = {};
this.rotation = 0;
this.scale = { x: 1, y: 1 };
this.stroke = true;
this.lineWidth = 2;
this.fill = true;
this.isOutOfBounds = false;
this.isAlive = true;
this.events = {
"onupdate": false,
"ondraw": false
};
}
/**
* Oppdaterer eventene i DrawableObject, altså sjekker om det er definert et event for 'onupdate' og kaller dette
* @param dt - Delta-tid
*/
DrawableObject.prototype.update = function (dt) {
if(this.events['onupdate']) this.events['onupdate'].call(this,dt);
};
/**
* Sjekker om det er noen eventer som skal kjøres 'ondraw'
* @param ctx - Gjeldene grafiske context
*/
DrawableObject.prototype.draw = function (ctx) {
if(this.events['ondraw']) this.events['ondraw'].call(this,ctx);
};
/**
* Skalerer, forflytter og roterer konteksten slik at tilbakekall-funksjonen kan inneholde lokale verdier i forhold til transmuteringene fra DrawableObject.
* @param ctx - Gjeldene kontekst
* @param callback - Tegningsfunksjon som tar inn en kontekst
*/
DrawableObject.prototype.prepAndDraw = function (ctx,callback) {
ctx.save();
ctx.scale(this.scale.x,this.scale.y);
ctx.translate(this.global.x,this.global.y);
ctx.rotate(this.rotation);
if(this.fill) ctx.fillStyle = this.color.fill;
if(this.stroke) {
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.color.stroke;
}
if(callback) callback.call(this,ctx);
ctx.restore();
};
/**
* Alle objekter som skal ha fart og akselerasjon må arve egenskapene til denne klassen, men denne klassen skal ikke konstruere objekter alene.
* @param {Number} x - Global x-posisjon
* @param {Number} y - Global y-posisjon
* @augments DrawableObject
* @constructor
*/
function MoveableObject(x, y) {
DrawableObject.call(this,x,y);
this.speed = { x: 0, y: 0, rotation: 0 };
this.acceleration = { x: 0, y: 0, rotation: 0 };
this.updateMovement = true;
}
/**
* Oppdaterer akselerasjon og hastighet
* @param dt - Delta-tid
*/
MoveableObject.prototype.update = function (dt) {
if(this.updateMovement) {
//Oppdaterer hastighet
this.speed.x += this.acceleration.x * dt;
this.speed.y += this.acceleration.y * dt;
this.speed.rotation += this.acceleration.rotation * dt;
//Oppdaterer posisjon
this.global.x += this.speed.x * dt;
this.global.y += this.speed.y * dt;
this.rotation += this.speed.rotation * dt;
}
};
//Bruker extend til å utvide prototypen til MoveableObject
MoveableObject.prototype.extend(DrawableObject.prototype);
/**
* Konstruerer tekst-objekter som kan tegnes på canvas.
* @param {Number} x - Global x-posisjon
* @param {Number} y - Global y-posisjon
* @param {String} text - Teksten som skal vises i
* @param {String} [color] - Fargen til teksten
* @augments DrawableObject
* @constructor
*/
function TextObject(x, y, text, color) {
DrawableObject.call(this,x,y);
this.text = text;
if(color) {
this.color.stroke = color;
this.color.fill = color;
}
this.local.x = 0;
this.local.y = 0;
this.font = {
size: 16,
family: "'Atari','Arial',sans-serif"
};
this.textAlign = "start";
this.textBaseline = "top";
this.stroke = false;
this.wrap = false;
this.maxWidth = 9999999;
this.lineSeparation = this.font.size/3;
}
/**
* Legger inn nye linjer utifra maks bredde og ny-linje symbolet
* @param ctx - Gjeldende kontekst
*/
TextObject.prototype.wrapText = function (ctx) {
var lines = this.text.split("\n"),
y = this.local.y;
for (var i = 0; i < lines.length; i++) {
var words = lines[i].split(' ');
var line = '';
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > this.maxWidth && n > 0) {
ctx.fillText(line, 0, y);
line = words[n] + ' ';
y += this.font.size + this.lineSeparation;
} else {
line = testLine;
}
}
ctx.fillText(line, 0, y);
y += this.font.size+this.lineSeparation;
}
};
/**
* Tegner teksten på gjeldende kontekst
* @param ctx - Gjeldende kontekst
*/
TextObject.prototype.draw = function (ctx) {
this.prepAndDraw(ctx,function (ctx) {
ctx.font = this.font.size + 'px ' + this.font.family;
ctx.textAlign = this.textAlign;
ctx.textBaseline = this.textBaseline;
if(this.wrap){
this.wrapText(ctx);
} else {
if(this.fill) ctx.fillText(this.text,this.local.x,this.local.y);
if(this.stroke) ctx.strokeText(this.text,this.local.x,this.local.y);
}
});
};
//Bruker extend til å utvide prototypen til TextObject
TextObject.prototype.extend(DrawableObject.prototype);
/**
* Konstruerer en firkant som kan tegnes på canvas.
* @param {Number} x - Global x-posisjon
* @param {Number} y - Global y-posisjon
* @param {object} pt1 - Lokal verdi for x og y til en av toppunktene i firkanten
* @param {object} pt2 - Lokal verdi for x og y til toppunktet som er diagonalt ovenfor pt1 i firkanten
* @param {String} [color] - Fargen til firkanten når den tegnes på canvas
* @augments DrawableObject
* @constructor
*/
function Rectangle(x, y, pt1, pt2, color) {
DrawableObject.call(this,x,y);
this.local = { pt1: pt1, pt2: pt2 };
this.width = Math.abs(pt2.x - pt1.x);
this.height = Math.abs(pt2.y - pt1.y);
if(color) {
this.color.stroke = color;
this.color.fill = color;
}
}
/**
* Tegner firkanten på gjeldende kontekst
* @param ctx - Gjeldende kontekst
*/
Rectangle.prototype.draw = function (ctx) {
this.prepAndDraw(ctx,function (ctx) {
if(this.fill) ctx.fillRect(this.local.pt1.x,this.local.pt1.y,this.width,this.height);
if(this.stroke) ctx.strokeRect(this.local.pt1.x,this.local.pt1.y,this.width,this.height);
});
};
//Bruker extend til å utvide prototypen til Rectangle
Rectangle.prototype.extend(DrawableObject.prototype);
/**
* Konstruerer et objekt som holder styr på brukergrensesnittet og som kan tegne enkelt elementer på canvas.
* @constructor
*/
function UI() {
this.unclickable = [];
this.clickable = [];
this.events = {
'onupdate': null,
'ondraw': null
}
}
/**
* Legger til et objekt til UI objektet på angitt lag, navn og om det er trykkbart eller ikke.
* @param {Number} atLayer - På lag nummer (Går fra 0 og oppover, der 0 er det øverste laget)
* @param {Boolean} clickable - Er objektet trykkbart (Har det et "onclick" event og "collisionWithPoint" metode)
* @param {String} name - Navnet til senere referanse
* @param {DrawableObject} object - Et objekt som kan tegnes
* @returns {DrawableObject} - Objektet som ble sent inn som parameter
*/
UI.prototype.addObject = function (atLayer,clickable,name,object) {
var arr = (clickable ? this.clickable : this.unclickable);
if(!arr[atLayer]) arr[atLayer] = {};
arr[atLayer][name] = object;
return object;
};
/**
* Finner et objekt basert på input verdier
* @param {String} name - Navnet til senere referanse
* @param {Number} [atLayer] - På lag nummer (Går fra 0 og oppover, der 0 er det øverste laget)
* @param {Boolean} [clickable] - Er objektet trykkbart (Har det et "onclick" event og "collisionWithPoint" metode)
* @returns {DrawableObject} - Funnet objekt
*/
UI.prototype.getObject = function (name, clickable, atLayer) {
var i,id,longest = Math.max(this.clickable.length,this.unclickable.length);
if(!atLayer) atLayer = 0;
if(atLayer >= longest) return false;
for(i = atLayer || 0; i < longest; i++){
if((clickable === true || clickable === undefined) && this.clickable[i]) {
for (id in this.clickable[i]) {
if(this.clickable[i].hasOwnProperty(id)) if(name == id) return this.clickable[i][id];
}
}
if((clickable === false || clickable === undefined) && this.unclickable[i]){
for (id in this.unclickable[i]) {
if(this.unclickable[i].hasOwnProperty(id)) if(name == id) return this.unclickable[i][id];
}
}
}
return false;
};
/**
* Fjerner et objekt basert på input verdier
* @param {String} name - Navnet til senere referanse
* @param {Number} [atLayer] - På lag nummer (Går fra 0 og oppover, der 0 er det øverste laget)
* @param {Boolean} [clickable] - Er objektet trykkbart (Har det et "onclick" event og "collisionWithPoint" metode)
* @returns {Boolean} - Om vellykket
*/
UI.prototype.removeObject = function (name, clickable, atLayer) {
var i,id,longest = Math.max(this.clickable.length,this.unclickable.length);
if(!atLayer) atLayer = 0;
if(atLayer >= longest) return false;
for(i = atLayer || 0; i < longest; i++){
if((clickable === true || clickable === undefined) && this.clickable[i]) {
for (id in this.clickable[i]) {
if(this.clickable[i].hasOwnProperty(id)) if(name == id) {
delete this.clickable[i][id];
return true;
}
}
}
if((clickable === false || clickable === undefined) && this.unclickable[i]){
for (id in this.unclickable[i]) {
if(this.unclickable[i].hasOwnProperty(id)) {
if (name == id) {
delete this.unclickable[i][id];
return true;
}
}
}
}
}
return false;
};
/**
* Oppdaterer alle objekter i UI'en
* @param dt - Delta-tid
*/
UI.prototype.update = function (dt) {
if(this.events['onupdate']) this.events['onupdate'].call(this,dt);
var i,id,longest = Math.max(this.clickable.length,this.unclickable.length);
for(i = 0; i < longest; i++){
if(this.clickable[i]) {
for (id in this.clickable[i]) {
if(this.clickable[i].hasOwnProperty(id)) if(this.clickable[i][id].update) this.clickable[i][id].update(dt);
}
}
if(this.unclickable[i]){
for (id in this.unclickable[i]) {
if(this.unclickable[i].hasOwnProperty(id)) if(this.unclickable[i][id].update) this.unclickable[i][id].update(dt);
}
}
}
};
/**
* Tegner alle objekter i UI'en i rekkefølgen: layer_n --> layer_0
* @param ctx - Gjeldende kontekst
*/
UI.prototype.draw = function (ctx) {
if(this.events['ondraw']) this.events['ondraw'].call(this,ctx);
var i,id,longest = Math.max(this.clickable.length,this.unclickable.length);
for(i = longest-1; i >= 0; i--){
if(this.clickable[i]) {
for (id in this.clickable[i]) {
if(this.clickable[i].hasOwnProperty(id)) if(this.clickable[i][id].draw) this.clickable[i][id].draw(ctx);
}
}
if(this.unclickable[i]){
for (id in this.unclickable[i]) {
if(this.unclickable[i].hasOwnProperty(id)) if(this.unclickable[i][id].draw) this.unclickable[i][id].draw(ctx);
}
}
}
};
/**
* Sjekker om alle klikkbare UI objekter kolliderer med punktet
* @param {Object} pt - Objekt med x og y verdier
* @returns {DrawableObject} - Hvis noe kolliderer så returneres det objektet, hvis ikke returneres false
*/
UI.prototype.checkCollisionWithPoint = function (pt) {
var current_layer;
for(var i = 0; i < this.clickable.length; i++) {
current_layer = this.clickable[i];
for (var name in current_layer) {
if (current_layer.hasOwnProperty(name)) {
if (current_layer[name].collisionWithPoint(pt)) return current_layer[name];
}
}
}
return false;
};
/**
* Lager knapper som kan trykkes på med tekst inni.
* @param {Number} x - Global x-posisjon
* @param {Number} y - Global y-posisjon
* @param {Number} width - Bredden til knappen
* @param {Number} height - Høyden til knappen
* @param {String} text - Teksten på knappen
* @param {Boolean} [defineFromCenter] - Definer knappen utifra midtpunktet, altså at de globale verdiene angir midtpunktet i firkanten.
* @param {String} [color] - Fargen på knappen
* @augments DrawableObject
* @constructor
*/
function Button(x, y, width, height, text, defineFromCenter, color) {
DrawableObject.call(this,(defineFromCenter ? x : x+width/2),(defineFromCenter ? y : y+height/2));
this.local.x = -width/2;
this.local.y = -height/2;
this.width = width;
this.height = height;
this.textObject = new TextObject(this.global.x,this.global.y,text);
this.textObject.textBaseline = "middle";
this.textObject.textAlign = "center";
this.events = {
'onclick': null,
'onhover': null
};
this.fill = false;
if(color){
this.color.stroke = color;
this.color.fill = color;
}
}
/**
* Oppdaterer knappen, da hovedsakelig posisjonen til teksten inne i knappen
* @param dt - Delta tid
*/
Button.prototype.update = function (dt) {
this.textObject.global.x = this.global.x;
this.textObject.global.y = this.global.y;
};
/**
* Tegner knappen på gjeldende kontekst
* @param ctx - Gjeldende kontekst
*/
Button.prototype.draw = function (ctx) {
this.prepAndDraw(ctx,function (ctx) {
if(this.fill) ctx.fillRect(this.local.x,this.local.y,this.width,this.height);
if(this.stroke) ctx.strokeRect(this.local.x,this.local.y,this.width,this.height);
});
this.textObject.draw(ctx);
};
/**
* Ser om punktet kolliderer med knappen
* @param {Object} pt - Objekt med x og y verdier
* @returns {Boolean}
*/
Button.prototype.collisionWithPoint = function (pt) {
return !(pt.x < this.global.x+this.local.x
|| pt.x > this.global.x+this.local.x+this.width
|| pt.y < this.global.y+this.local.y
|| pt.y > this.global.y+this.local.y+this.height);
};
Button.prototype.extend(DrawableObject.prototype);
/**
* En klasse som holder styr på bakken i spillet og kan tegne denne på canvas.
* @param x - Global x-posisjon
* @param y - Global y-posisjon
* @param path - En array med objekter som har x og y verdier
* @augments DrawableObject
* @constructor
*/
function Terrain(x, y, path) {
DrawableObject.call(this,x,y);
this.path = path;
this.fill = false;
this.bonus_time_limit = 0;
this.lineWidth = 3;
}
/**
* Finner høyden til terrenget ved gitt x-verdi
* @param x - x-verdi der man skal finne høyden
* @returns {Number} - For y-verdien til gitt x-verdi
*/
Terrain.prototype.getTerrianHeight = function (x) {
for(var i = 1; i < this.path.length; i++){
if(this.path[i-1].x < x && x < this.path[i].x) return calc.geometry.interpolation(this.path[i-1].x,this.path[i-1].y,this.path[i].x,this.path[i].y,x);
}
};
/**
* Finner hellningen til bakken i gitt x-verdi
* @param {Number} x - X-verdi
* @returns {Number} - Stigning i punktet x
*/
Terrain.prototype.getGradient = function (x) {
for(var i = 1; i < this.path.length; i++){
if(this.path[i-1].x < x && x < this.path[i].x) return calc.geometry.gradient(this.path[i-1],this.path[i]);
}
};
/**
* Tegner terrenget på gitt kontekst
* @param ctx - Gitt kontekst
*/
Terrain.prototype.draw = function (ctx) {
this.prepAndDraw(ctx,function (ctx) {
ctx.beginPath();
ctx.moveTo(this.path[0].x,this.path[0].y);
for(var i = 1; i < this.path.length; i++){
ctx.lineTo(this.path[i].x,this.path[i].y);
}
if(this.stroke) ctx.stroke();
if(this.fill) ctx.fill();
});
};
Terrain.prototype.extend(DrawableObject.prototype);
/**
* Konstruerer objektet som symboliserer spilleren, altså månelanderen, og kan tegne denne på canvas.
* @param x - Global x-posisjon
* @param y - Global y-posisjon
* @param [width] - Bredden til landeren, hvis ikke definert så er standaren 100
* @param [height] - Høyden til landeren, hvis ikke definert så er standaren 100
* @augments MoveableObject
* @constructor
*/
function Lander(x, y, width, height) {
MoveableObject.call(this,x,y);
this.width = width || 100;
this.height = height || 100;
this.fill = false;
this.color.fill = 'white';
this.lineWidth = 6;
this.fuel = CONST.LANDER.STARTING_FUEL;
this.isExploded = false;
this.thruster = {
sound: new Sound("Thrusters.wav"),
x: 0,
y: 0,
cache: {
cos: 1,
sin: 0
},
rotation: 0,
amount: 0,
thrust: function (amount,conserve) {
this.amount = amount + (conserve ? this.amount : 0);
if(this.amount > this.amount_limit) this.amount = this.amount_limit;
else if(this.amount < 0) this.amount = 0;
this.update(true);
},
update: function (usePrev) {
if(usePrev !== true){
this.cache.cos = Math.cos(this.rotation);
this.cache.sin = Math.sin(this.rotation);
}
this.x = this.cache.sin * this.amount;
this.y = this.cache.cos * this.amount;
this.sound.nodes.gainNode.gain.value = 0.3*this.amount/this.amount_limit;
},
amount_limit: 1.7*CONST.PHYSICS.GRAVITATION,
path2D: (function () {
return new Path2D("M23.018 20.705l135.64 163.623-107.33-32.39 168.79 111.326L82.784 224.11l192.51 111.87-130.525-1.76 282.08 126.116c13.913 7.198 28.182 13.638 42.728 19.246l2.297.885 20.797 9.3-16.895-37.82c-3.67-9.115-7.69-18.094-12.03-26.926L338.312 144.24l1.094 129.362L228.352 82.393l38.482 136.49L155.906 50.668l31.684 106.467L23.018 20.705zm225.148 225.178c94.262 38.75 169.608 116.195 208.152 207.924-91.01-40.827-168.835-115.908-208.152-207.924z");
})(),
path2DOffset: { x: -550, y: -550 },
path2DScale: { x: 2*this.width/512, y: 2*this.height/512 },
path2DRotation: 5*Math.PI/4,
draw: function (ctx) {
ctx.save();
ctx.rotate(this.path2DRotation);
ctx.scale(this.path2DScale.x*this.amount/this.amount_limit,this.path2DScale.y*this.amount/this.amount_limit);
ctx.translate(this.path2DOffset.x+5*Math.random()*2-1,this.path2DOffset.y+5*Math.random()*2-1);
ctx.stroke(this.path2D);
ctx.restore();
}
};
this.thruster.sound.events.onloaded = function () {
this.source.loop = true;
this.nodes.gainNode.gain.value = 0;
this.play();
};
this.explosion = {
sound: new Sound("Explosion.wav"),
global: { x: 0, y: 0 },
path2D: (function () {
return new Path2D("M340.625 18.438l-42.438 104.657-39.562-99.938L213.25 157l-75.97-54.78 14.22 92.53L24.53 27l108.095 202.032-72.094-36.344 59.532 171.188-88.906-12.53 55.25 72.06-52.47-12.03 103.626 78.75 1.875 2.47h240.188l110.28-151.376-52.03 5.468 56.406-67.562-71.718 36.03L459.97 203.22l-54.783 24.625-88.75 67.843 54.282-78.25 18.936-116.343-57.75 37.562 8.72-120.22zM310.312 204.25L296.72 317.127l82.53-21.5-59.47 57.625L376.907 395l-77.437-12.905 36.092 75.75-67-39.313-40.593 50.375-3.72-57.97-70.063 5.783 70.063-37.313-77.53-79.28 75.124 18.56-8.375-84.75 51.405 87.5 45.437-117.186z");
})(),
path2DOffset: { x: -512/2, y: -800/2 },
path2DScale: { x: 4*this.width/512, y: 4*this.height/512 },
path2DRotation: 0,
animating: false,
size: 0,
animationPlaytime: 1.2,
animateSize: (function () {
var progress = 0.0001, dir = 1, playSound = true;
return function (dt) {
if(playSound){
this.sound.play();
playSound = false;
}
this.animating = true;
if(progress > this.animationPlaytime/2) dir = -1;
if(progress > 0) progress += dt*dir;
else {
progress = 0;
this.animating = false;
}
this.size = 2*progress/this.animationPlaytime;
};
})(),
draw: function (ctx) {
ctx.save();
ctx.translate(this.global.x,this.global.y);
ctx.scale(this.path2DScale.x*this.size,this.path2DScale.y*this.size);
ctx.translate(this.path2DOffset.x+4*(Math.random()*2-1),this.path2DOffset.y+4*(Math.random()*2-1));
ctx.rotate(this.path2DRotation);
ctx.strokeStyle = 'white';
ctx.lineWidth = 5;
ctx.stroke(this.path2D);
ctx.restore();
}
};
this.path2DOffset = { x: -512/2, y: -512/2 };
this.path2DScale = { x: this.width/512, y: this.height/512 };
this.path2D = (function(){
return new Path2D("M144 23c-9.282 0-17 7.718-17 17 0 5.99 3.224 11.317 8 14.35v40.46l-15.156 7.38L91.68 228.93l76.5-12.75 15.238-91.434 48.268-77.014L153 86.047V54.35c4.776-3.033 8-8.36 8-14.35 0-9.282-7.718-17-17-17zm112 19.832L202.62 128h106.76L256 42.832zM416 45c-24.96 0-45 20.04-45 45s20.04 45 45 45 45-20.04 45-45-20.04-45-45-45zm-135.686 2.732l48.268 77.014 15.068 90.414 76.637 13.617-17.135-77.105C374.586 145.708 353 120.287 353 90c0-2.263.126-4.497.36-6.7l-73.046-35.568zM416 71c9.282 0 17 7.718 17 17s-7.718 17-17 17-17-7.718-17-17 7.718-17 17-17zm-288 48h32v18h-32v-18zm64 89l16 32h96l16-32H192zm149.88 25.13l-39.46 92.067 91.148-13.35 25.967-64.92-77.656-13.798zm-171.468.925L92.51 247.04l25.922 64.806 89.494 13.11-37.514-90.9zM198.942 256l29.71 71.992L256 331.998l25.527-3.74L312.494 256h-113.55zm-84.962 73.385L58.15 455H32v18h64v-18H77.85l6.543-14.72c.154-.06.285-.122.486-.18 1.41-.413 3.09-.958 5.097-1.637 4.014-1.358 9.296-3.25 15.464-5.514 12.34-4.53 28.187-10.538 43.86-16.546 18.96-7.267 34.964-13.486 46.782-18.093L247 436.5V471h-23v18h64v-18h-23v-34.5l50.918-38.19c11.818 4.608 27.82 10.827 46.78 18.094 15.675 6.008 31.523 12.017 43.86 16.545 6.17 2.263 11.45 4.155 15.465 5.513 2.007.68 3.687 1.224 5.098 1.637.202.058.333.12.487.18L434.15 455H416v18h64v-18h-26.15l-55.83-125.613-18.493 2.71 38.096 85.717c-1.6-.578-3.097-1.116-4.86-1.763-12.184-4.47-27.99-10.462-43.622-16.454-14.304-5.483-28.13-10.84-39.288-15.176l24.304-48.608-21.715 3.18L314.44 375H265v-26.13l-9 1.32-9-1.32V375h-49.438l-18.003-36.008-21.718-3.18 24.304 48.608c-11.156 4.337-24.983 9.693-39.287 15.176-15.633 5.992-31.44 11.983-43.622 16.455-1.764.648-3.26 1.186-4.86 1.764l38.095-85.72-18.493-2.71zM219 393h28v21l-28-21zm46 0h28l-28 21v-21z")})();
this.boundingRect = new Rectangle(x+this.width/2,y+this.height/2,{x:-this.width/2,y:-this.height/2},{x:this.width/2,y:this.height/2});
}
/**
* Sjekker om månelanderen har overlevd landingen basert på kriterier
* @param terrain - Terrenget månelanderen har landet på
* @returns {Boolean} - Om månelanderen har overlevd
*/
Lander.prototype.hasSurvivedLanding = function (terrain) {
this.thruster.thrust(0);
var terrainGradient = terrain.getGradient(this.global.x);
if(Math.abs(this.speed.y) > CONST.LANDER.LANDING_REQUIREMENTS.SPEED.VERTICAL) return false;
if(Math.abs(this.speed.x) > CONST.LANDER.LANDING_REQUIREMENTS.SPEED.HORIZONTAL) return false;
if(Math.abs(terrainGradient) > CONST.LANDER.LANDING_REQUIREMENTS.GROUND_GRADIANT) return false;
if(Math.abs((this.rotation % 2*Math.PI)) > CONST.LANDER.LANDING_REQUIREMENTS.ANGLE) return false;
this.rotation = 0;
return true;
};
/**
* Oppdaterer månelanderen
* @param dt - Delta-tid
*/
Lander.prototype.update = function (dt) {
//Oppdaterer posisjonen til rektangelet rundt landeren
this.boundingRect.global.x = this.global.x;
this.boundingRect.global.y = this.global.y;
this.boundingRect.rotation = this.rotation;
//Fjerner like mye bensin som brukes per sekund
this.fuel -= this.thruster.amount*dt;
if(this.fuel <= 0) {
this.thruster.thrust(0);
this.fuel = 0;
}
//Oppdaterer rotasjonen til thrusteren, bare når rotasjonen endrer seg
if(this.thruster.rotation != this.rotation) {
this.thruster.rotation = this.rotation;
this.thruster.update(false);
}
//Oppdaterer akselerasjonen til landeren
this.acceleration.y = CONST.PHYSICS.GRAVITATION - this.thruster.y;
this.acceleration.x = this.thruster.x;
//Spiller eksplosjonsanimasjonen
if(this.isExploded) {
this.explosion.global.x = this.global.x;
this.explosion.global.y = this.global.y;
this.explosion.animateSize(dt);
}
};
/**
* Tegner månelanderen og dens effekter
* @param ctx - Gjeldende kontekst
*/
Lander.prototype.draw = function (ctx) {
this.prepAndDraw(ctx,function (ctx) {
if(!this.isExploded) {
this.thruster.draw(ctx);
ctx.scale(this.path2DScale.x, this.path2DScale.y);
ctx.translate(this.path2DOffset.x, this.path2DOffset.y);
if (this.stroke) ctx.stroke(this.path2D);
if (this.fill) ctx.fill(this.path2D);
}
});
if(this.explosion.animating) this.explosion.draw(ctx);
if(DEBUG) this.boundingRect.draw(ctx);
if(DEBUG) calc.geometry.point.draw(this.global,ctx,CONST.PHYSICS.METER/2,"white");
};
Lander.prototype.extend(MoveableObject.prototype);
/**
* En funksjon som kan laste inn, legge til ulike "noder" og spille lyd
* @param name - Navnet til lyden inne i ressursmappen for lyd (Se CONST.APP.RESOURCE_FOLDERS)
* @constructor
*/
function Sound(name) {
this.name = name;
this.url = CONST.APP.RESOURCE_FOLDERS.AUDIO+name;
this.buffer = null;
this.source = null;
this.nodes = {
gainNode: App.audio.ctx.createGain()
};
this.events = {
'onloaded': null
};
this.load();
}
/**
* Laster inn lyden asynkront og gjør den klar til å spilles
*/
Sound.prototype.load = function () {
var request = new XMLHttpRequest();
request.open('GET',this.url,true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
App.audio.ctx.decodeAudioData(request.response, function(buffer) {
this.buffer = buffer;
this.source = App.audio.ctx.createBufferSource(); // creates a sound source
this.source.buffer = buffer; // tell the source which sound to play
var prevNode = null;
for(var name in this.nodes){
if(this.nodes.hasOwnProperty(name)){
prevNode = this.nodes[name];
this.source.connect(prevNode);
}
}
prevNode.connect(App.audio.ctx.destination); // connect the source to the context's destination (the speakers)
if(this.events.onloaded) this.events.onloaded.call(this);
}.bind(this), function () {
console.error("Error loading song.");
});
}.bind(this);
request.send();
};
/**
* Spiller av lyden
* @param atTime - Start verdi
*/
Sound.prototype.play = function(atTime) {
this.source.start(atTime || 0);
};
/**
* Stopper å spille lyden
*/
Sound.prototype.stop = function () {
this.source.stop();
};
/**
* Inneholder alle konstante variabler og kan brukes til å endre på spillet/justere mekanikker.
* @type {Object}
*/
var CONST = {
MAPS: null,
GAME: {
SCORES: {
FUEL_WORTH: 3,
BONUS_TIME_WORTH: 1
}
},
CONTROLS: {
THRUST: {
INCREASE: "W",
DECREASE: "S"
},
ROTATE: {
LEFT: "A",
RIGHT: "D"
}
},
APP: {
RESOURCE_FOLDERS: {
AUDIO: "res/sounds/"
},
TITLE: "Maanelander",
SIZE: {
WIDTH: 700,
HEIGHT: 500,
FIT_SCREEN: false
}
},
PHYSICS: {
METER: 4,
GRAVITATION: 6.5
},
LANDER: {
LANDING_REQUIREMENTS: {
ANGLE: 0.8,
SPEED: {
VERTICAL: 11,
HORIZONTAL: 4
},
GROUND_GRADIANT: 0.6
}
}
};
/**
* Hoved applikasjonen, altså spillet.
* @type {Object}
*/
var App = {
canvases: {
foreground: null,
background: null,
UI: null,
tmp: document.createElement('canvas')
},
ctxs: {},
audio: {
ctx: null
},
animationID: null,
clock: {
startTime: Date.now(),
lastTime: Date.now(),
currentTime: Date.now(),
multiplicator: 1,
delta: 0,
totalTime: function () {
return Date.now() - this.startTime;
},
update: function () {
this.currentTime = Date.now();
this.delta = ((this.currentTime - this.lastTime) / 1000) * this.multiplicator;
this.lastTime = this.currentTime;
}
}, //Klokken som holder styr på tiden (Som nevnt i dokumentasjon)
stateHandler: {
current: null,
states: {
MENU: {
events_last_tick: [],
events: {
'onclick': function (e,state) {
var mouseUIPos = calc.input.mouse.getPos(e,App.canvases.UI);
var res = state.UI.checkCollisionWithPoint(mouseUIPos);
if(res) res.events.onclick.call(res,e,state);
},
'onmousemove': function (e, state) {
var mouseUIPos = calc.input.mouse.getPos(e,App.canvases.UI);
var res = state.UI.checkCollisionWithPoint(mouseUIPos);
if(res) App.canvases.UI.style.cursor = "pointer";
else App.canvases.UI.style.cursor = "default";
}
},
UI: new UI(),
init: function () {
App.canvases.UI.style.cursor = "default";
//Hoved overskrift
var headline = this.UI.addObject(0, false, 'headline', new TextObject(0, 0, CONST.APP.TITLE, "white"));
headline.textBaseline = "middle";
headline.textAlign = "center";
headline.font.size = 24;
headline.events.onupdate = function (dt) {
this.global.x = App.width / 2;
this.global.y = App.height / 3;
};
//Start knapp
var start_button = this.UI.addObject(0,true,'start_button',new Button(App.width/2,App.height/2,100,40,"START",true,'white'));
start_button.textObject.color.fill = 'white';
start_button.events.onclick = function (e) {
this.fill = true;
this.textObject.color.fill = 'black';
setTimeout(function(){
App.stateHandler.change(App.stateHandler.states.IN_GAME, { showTutorial: true, mapName: 'LEVEL1' });
},200);
};
//Instruksjonsknappen
var how_to_play_button = this.UI.addObject(0,true,'how_to_play_button',new Button(App.width/2,App.height*5/8,250,40,"INSTRUKSJONER",true,'white'));
how_to_play_button.textObject.color.fill = 'white';
how_to_play_button.events.onclick = function (e) {
this.fill = true;
this.textObject.color.fill = 'black';
setTimeout(function(){
App.stateHandler.change(App.stateHandler.states.IN_GAME,{ showTutorial: true, mapName: 'TUTORIAL' });
},200);
};
//Editor-knappen
var editor_button = this.UI.addObject(0,true,'editor_button',new Button(App.width/2,App.height*6/8,120,40,"EDITOR",true,'white'));
editor_button.textObject.color.fill = 'white';
editor_button.events.onclick = function (e) {
this.fill = true;
this.textObject.color.fill = 'black';
setTimeout(function(){
App.stateHandler.change(App.stateHandler.states.EDITOR);
},200);
};
//Logoer og design på menyen
var logo_lander = this.UI.addObject(10,false,'logo_lander',new Lander(100,100,200,200));
logo_lander.events.onupdate = function (dt) {
if(this.global.y+this.height/2 > App.height){
this.acceleration.y = 0;
this.speed.y = 0;
}
};
var logo_lander2 = this.UI.addObject(10,false,'logo_lander2',new Lander(App.width-100,100,200,200));
logo_lander2.events.onupdate = function (dt) {
if(this.global.y+this.height/2 > App.height){
this.acceleration.y = 0;
this.speed.y = 0;
}
};
},
update: function (dt) {
this.UI.update(dt);
},
draw: function () {
this.UI.draw(App.ctxs.UI);
}
},
IN_GAME: {
sounds: {},
current_options: null,
player: {
score: 0,
startTime: 0,
endTime: 0,
playTime: 0,
playing: true,
calculateScore: function (lander, terrain) {
this.score = Math.floor(lander.fuel * CONST.GAME.SCORES.FUEL_WORTH + (this.playTime < terrain.bonus_time_limit ? (this.playTime-terrain.bonus_time_limit) * CONST.GAME.SCORES.BONUS_TIME_WORTH : 0));
}
},
events_last_tick: [],
events: {
'onkeydown': function (e, state) {
if(!state.keys[String.fromCharCode(e.keyCode).toUpperCase()]) state.keys[String.fromCharCode(e.keyCode).toUpperCase()] = true;
},
'onkeyup': function (e, state) {
state.keys[String.fromCharCode(e.keyCode).toUpperCase()] = false;
},
'onclick': function (e, state) {
var mouseUIPos = calc.input.mouse.getPos(e,App.canvases.UI);
var res = state.UI.checkCollisionWithPoint(mouseUIPos);
if(res) res.events.onclick.call(res,e,state);
},
'onmousemove': function (e, state) {
var mouseUIPos = calc.input.mouse.getPos(e,App.canvases.UI);
var res = state.UI.checkCollisionWithPoint(mouseUIPos);
if(res) App.canvases.UI.style.cursor = "pointer";
else App.canvases.UI.style.cursor = "default";
},
'landerHitGround': function (e, state) {
state.player.endTime = Date.now();
state.player.playing = false;
e.lander.thruster.sound.stop();
var title, message;
if(e.lander.hasSurvivedLanding(e.terrain)){
state.player.calculateScore(e.lander,e.terrain);
state.sounds.hasLanded.play();
title = "GRATULERER!";
message = "Du fikk "+state.player.score+" poeng.\nVil du spille igjen eller gaa tilbake til menyen?";
} else {
title = "DU EKSPLODERTE!";
message = "Ingen poeng for det. Bedre lykke neste gang!\nPS! Husk at det er en dyr romsonde du styrer, bruk hodet!";
e.lander.isExploded = true;
}
setTimeout(function () {
var overlay = state.UI.addObject(3,false,'overlay',new Rectangle(0,0,{x:0,y:0},{x:App.width,y:App.height},'rgba(0,0,0,0.8)'));
var post_game_title = state.UI.addObject(0,false,'post_game_title',new TextObject(App.width/2,App.height/3,title,'white'));
post_game_title.textBaseline = "middle";
post_game_title.textAlign = "center";
post_game_title.font.size = 20;
var post_game_message = state.UI.addObject(0,false,'post_game_message',new TextObject(App.width/2,App.height*2/5,message,'white'));
post_game_message.textBaseline = "middle";
post_game_message.textAlign = "center";
post_game_message.wrap = true;
post_game_message.font.size = 11;
var play_again_button = state.UI.addObject(0,true,'play_again_button',new Button(App.width/3,App.height*2/3,190,50,"SPILL IGJEN",true,"white"));
play_again_button.textObject.color.fill = "white";
play_again_button.events.onclick = function (e, state) {
this.fill = true;
this.textObject.color.fill = 'black';
setTimeout(function(){
App.stateHandler.change(App.stateHandler.states.IN_GAME, { showTutorial: false, mapName: App.stateHandler.states.IN_GAME.current_options.mapName });
},200);
};
var quit_to_menu_button = state.UI.addObject(0,true,'quit_to_menu_button',new Button(App.width*2/3,App.height*2/3,220,50,"GAA TIL MENYEN",true,"white"));
quit_to_menu_button.textObject.color.fill = "white";
quit_to_menu_button.events.onclick = function (e, state) {
this.fill = true;
this.textObject.color.fill = 'black';
setTimeout(function(){
App.stateHandler.change(App.stateHandler.states.MENU);
},200);
};
},1000);
}
},
UI: new UI(),
keys: {},
key_events: {},
gameObjects: [],
init: function (options) {
this.sounds.liftOff = new Sound("WeHaveLiftOff.wav");
this.sounds.hasLanded = new Sound("TheEagleHasLanded.wav");
this.current_options = options;
this.player.startTime = Date.now();
this.player.endTime = Date.now();
this.player.playing = true;
App.canvases.UI.style.cursor = "default";
//Lager både lander og terrain som symboliserer månelanderen og bakken (Noen av verdien definert i maps.json)
var lander = new Lander(CONST.MAPS[options.mapName].LANDER.STARTING_POS.X,CONST.MAPS[options.mapName].LANDER.STARTING_POS.Y,5*CONST.PHYSICS.METER,5*CONST.PHYSICS.METER),
terrain = new Terrain(0,0,CONST.MAPS[options.mapName].PATH);
terrain.bonus_time_limit = CONST.MAPS[options.mapName].BONUS_TIME_LIMIT;
lander.fuel = CONST.MAPS[options.mapName].LANDER.STARTING_FUEL;
lander.events.onupdate = function () {
var groundY = terrain.getTerrianHeight(this.global.x);
if(this.boundingRect.global.y + this.boundingRect.local.pt2.y > groundY){
this.global.y = -this.width/2 + groundY;
lander.updateMovement = false;
App.stateHandler.current.events_last_tick.push({event:{lander:lander,terrain:terrain},event_type:'landerHitGround'});
} else if (this.global.y - this.height/2 < 0){
this.acceleration.y = 0;
this.speed.y = 0;
this.global.y = this.height/2;
}
if(this.global.x - this.width/2 < 0){
this.acceleration.x = 0;
this.speed.x = 0;
this.global.x = this.width/2;
} else if (this.global.x + this.width/2 > App.width){
this.acceleration.x = 0;
this.speed.x = 0;
this.global.x = -this.width/2 + App.width;
}
};
this.gameObjects.push(lander);
this.gameObjects.push(terrain);
//Definerer kontrollene
this.key_events[CONST.CONTROLS.THRUST.INCREASE] = function (dt) {
lander.thruster.thrust(CONST.PHYSICS.METER*dt,true);
};
this.key_events[CONST.CONTROLS.THRUST.DECREASE] = function (dt) {
lander.thruster.thrust(-CONST.PHYSICS.METER*dt,true);
};
this.key_events[CONST.CONTROLS.ROTATE.LEFT] = function (dt) {
lander.rotation -= CONST.PHYSICS.METER/5*dt;
};
this.key_events[CONST.CONTROLS.ROTATE.RIGHT] = function (dt) {
lander.rotation += CONST.PHYSICS.METER/5*dt;
};
//Lager UI'en som vises i spillet
var UIText = {
speed_vertical: {
value: this.UI.addObject(5, false, 'speed_vertical_value', new TextObject(App.width - 90, 25, "0", 'white')),
label: this.UI.addObject(5, false, 'speed_vertical_label', new TextObject(App.width - 330, 25, "Vertikal hastighet", 'white'))
},
speed_horizontal: {
value: this.UI.addObject(5, false, 'speed_horizontal_value', new TextObject(App.width - 90, 50, "0", 'white')),
label: this.UI.addObject(5, false, 'speed_horizontal_label', new TextObject(App.width - 330, 50, "Horisontal hastighet", 'white'))
},
altitude: {
value: this.UI.addObject(5, false, 'altitude_value', new TextObject(App.width - 90, 75, "0", 'white')),
label: this.UI.addObject(5, false, 'altitude_label', new TextObject(App.width - 330, 75, "Hoeyde", 'white'))
},
score: {
value: this.UI.addObject(5, false, 'score_value', new TextObject(180, 25, "0", 'white')),
label: this.UI.addObject(5, false, 'score_label', new TextObject(40, 25, "Poengsum", 'white'))
},
fuel: {
value: this.UI.addObject(5, false, 'fuel_value', new TextObject(180, 50, "0", 'white')),
label: this.UI.addObject(5, false, 'fuel_label', new TextObject(40, 50, "Bensin", 'white'))
},
time: {
value: this.UI.addObject(5, false, 'time_value', new TextObject(180, 75, "00:00", 'white')),
label: this.UI.addObject(5, false, 'time_label', new TextObject(40, 75, "Tid", 'white'))
}
};
for(var name in UIText){
if(UIText.hasOwnProperty(name)){
UIText[name].value.font.size = 11;
UIText[name].label.font.size = 11;
}
}
this.UI.events.onupdate = function (dt) {
UIText.speed_vertical.value.text = (lander.speed.y/CONST.PHYSICS.METER).toFixed(1)*-1+" m/s";
UIText.speed_horizontal.value.text = (lander.speed.x/CONST.PHYSICS.METER).toFixed(1)+" m/s";
var cache;
UIText.altitude.value.text = ((cache = Math.floor((terrain.getTerrianHeight(lander.global.x)-lander.global.y-lander.height/2)/CONST.PHYSICS.METER)) > 0 ? cache : 0)+" m";
UIText.fuel.value.text = Math.floor(lander.fuel)+" liter";
UIText.time.value.text = calc.clock.secondsToMMSS(App.stateHandler.current.player.playTime);
UIText.score.value.text = App.stateHandler.current.player.score;
};
//Lager en tutorial som kjøres hvis det er spesifisert i stateOptions
if(options.showTutorial){
App.clock.multiplicator = 0;
var tutorialUI = {
overlay: this.UI.addObject(3,false,'tutorial_overlay',new Rectangle(0,0,{x:0,y:0},{x:App.width,y:App.height},'rgba(0,0,0,0.8)')),
box: this.UI.addObject(2,false,'tutorial_box',new Rectangle(App.width/2,App.height*3/7,{x:-230,y:-140},{x:230,y:140},'rgba(0,0,0,0.8)')),
title: this.UI.addObject(1,false,'tutorial_title',new TextObject(App.width/2,App.height/4,"",'white')),
message: this.UI.addObject(1,false,'tutorial_message',new TextObject(App.width/2,App.height*11/32,"",'white')),
knapp: this.UI.addObject(1,true,'tutorial_knapp',new Button(App.width/2,App.height*5/8,140,40,"NESTE",true,'white'))
};
tutorialUI.box.color.stroke = "white";
tutorialUI.box.lineWidth = 4;
tutorialUI.title.textBaseline = "middle";
tutorialUI.title.textAlign = "center";
tutorialUI.title.maxWidth = 400;
tutorialUI.title.wrap = true;
tutorialUI.message.textBaseline = "middle";
tutorialUI.message.textAlign = "center";
tutorialUI.message.maxWidth = 400;
tutorialUI.message.font.size = 12;
tutorialUI.message.wrap = true;
tutorialUI.knapp.textObject.color.fill = "white";
var slides = [
{
title: "Velkommen til "+CONST.APP.TITLE,
message: "Du skal faa kontrollere en hoeymoderne maanelander som skal utforske nye planeter. Men foer morroa begynner, saa maa du laere deg hvordan den skal styres!"
},
{
title: "AA bruke rakettene 1",
message: "En viktig del av landingen er avpassing av fart i vertikal retning. For aa faa stoerre oppdrift trykker man paa:\n\n"+CONST.CONTROLS.THRUST.INCREASE
},
{
title: "",
message: "Vaar naavaerende romsonde taaler bare nedslag paa om lag "+(CONST.LANDER.LANDING_REQUIREMENTS.SPEED.VERTICAL/CONST.PHYSICS.METER).toFixed(1)+" meter i sekundet."
},
{
title: "AA bruke rakettene 2",
message: "Men for stor oppdrift er jo ikke bra, vi skal jo lande! Derfor kan du minke oppdriften med: \n\n"+CONST.CONTROLS.THRUST.DECREASE
},
{
title: "Planeter er ikke flate",
message: "Selv om man skulle oenske planeten var helt flat slik at man kunne landet enkelt, saa er de ikke det. Derfor kan du rotere romsonden med:\n\n"+CONST.CONTROLS.ROTATE.LEFT+" og "+CONST.CONTROLS.ROTATE.RIGHT
},
{
title: "Bensin koster penger",
message: "Den allerede dyre romsonden sluker dyrt rakett-drivstoff. Derfor saa boer du proeve aa bruke saa lite drivstoff som mulig. Du vil bli belønnet for dette."
},
{
title: "Tid er penger",
message: "Ikke bare maa du bruke lite drivstoff, men du boer ogsaa bruke saa lite tid som mulig. Tid er penger (og poeng)."
},
{
title: "Lykke til!",
message: "Som sagt er ikke planeter flate, med det er landingsbasen vaar. Proev deg fram!"
}
];
var nextSlide = (function () {
var index = 0, len = slides.length;
return function () {
if (index == len){
for(var name in tutorialUI){
if(tutorialUI.hasOwnProperty(name)){
App.stateHandler.current.UI.removeObject("tutorial_"+name);
}
}
App.stateHandler.states.IN_GAME.sounds.liftOff.play();
setTimeout(function () {
App.clock.multiplicator = 1;
},3300);
}
else {
tutorialUI.title.text = slides[index].title;
tutorialUI.message.text = slides[index].message;
}
if(index < len) index++;
};
})();
nextSlide();
tutorialUI.knapp.events.onclick = nextSlide;
}
},
update: function (dt) {
if(this.player.playing) this.player.playTime += dt;
for(var key_name in this.keys) if(this.keys.hasOwnProperty(key_name) && this.keys[key_name] && this.key_events[key_name]) this.key_events[key_name](dt);
this.UI.update(dt);
for(var i = 0; i < this.gameObjects.length; i++){
this.gameObjects[i].update(dt);
}
},
draw: function () {
this.UI.draw(App.ctxs.UI);
for(var i = 0; i < this.gameObjects.length; i++){
this.gameObjects[i].draw(App.ctxs.foreground);
}
},
reset: function () {
this.UI = new UI();
this.player.score = 0;
this.player.playTime = 0;
this.gameObjects = [];
}
},
EDITOR: {
UI: new UI(),
path: [],
events_last_tick: [],
events: {
'onclick': function (e, state) {
var mousePos = calc.input.mouse.getPos(e,App.canvases.foreground);
var res = state.UI.checkCollisionWithPoint(mousePos);
if(res) res.events.onclick.call(res,e,state);
else state.path.push(mousePos);
},
'onmousemove': function (e, state) {
var mouseUIPos = calc.input.mouse.getPos(e,App.canvases.UI);
var res = state.UI.checkCollisionWithPoint(mouseUIPos);
if(res) App.canvases.UI.style.cursor = "pointer";
else App.canvases.UI.style.cursor = "default";
},
'onkeyup': function (e, state) {
if(e.keyCode == 82 && state.path.length > 0) {
state.path = state.path.slice(0,state.path.length-1);
}
}
},
init: function () {
App.canvases.UI.style.cursor = "default";
var overlay = this.UI.addObject(3,false,'overlay',new Rectangle(0,0,{x:0,y:0},{x:App.width,y:App.height},"rgba(0,0,0,0.7)"));
//Lager overskriften i editoren
var headline = this.UI.addObject(2,false,'headline',new TextObject(App.width/2,App.height/5,"Velkommen til editoren","white"));
headline.font.size = 20;
headline.textBaseline = "middle";
headline.textAlign = "center";
//Lager meldingen som vises i editoren
var message = this.UI.addObject(2,false,'message',new TextObject(App.width/2,App.height/3,"Ved aa trykke rundt paa canvaset kan du lage en bane. For aa eksportere banen saa trykker du paa eksporter oppe i hoeyre hjoerne. Du kan bruke R til aa angre ditt nyligste punkt.","white"));
message.font.size = 12;
message.textBaseline = "middle";
message.textAlign = "center";
message.wrap = true;
message.maxWidth = 400;
//Lager knappen som man kan fjerne teksten på skjermen med
var ok_button = this.UI.addObject(2,true,'ok_button',new Button(App.width/2,App.height*3/5,165,45,"OK",true,"white"));
ok_button.textObject.color.fill = "white";
ok_button.events.onclick = function (e,state) {
this.fill = true;
this.textObject.color.fill = 'black';
setTimeout(function(){
state.UI.removeObject('ok_button');
state.UI.removeObject('overlay');
state.UI.removeObject('headline');
state.UI.removeObject('message');
},200);
};
//Lager knappen som eksporterer kreasjonen
var export_button = this.UI.addObject(5,true,'export_button',new Button(App.width - 100,37,165,45,"EKSPORTER",true,"white"));
export_button.textObject.color.fill = "white";
export_button.events.onclick = function (e,state) {
this.fill = true;
this.textObject.color.fill = 'black';
setTimeout(function(){
do {
var level_name = prompt("Hva skal banen hete?\n\n(Bare store bokstaver fra A-Z og tall)");
if(level_name === null) break;
} while(!/^[A-Z0-9]*$/.test(level_name));
if(level_name !== null){
if(!CONST.MAPS[level_name]) {
var res = confirm("Er du sikker på at du vil lagre banen din som:\n\n" + level_name);
if (res) {
CONST.MAPS[level_name] = {};
CONST.MAPS[level_name].path = state.path;
console.log(JSON.stringify(state.path));
alert("Bane lagret som:\n\n" + level_name);
}
else alert("Bane ikke lagret.\nUheldigvis bare midlertidig...");
} else alert("Denne banen finnes allerede. Prøv et annet navn.");
}
this.fill = false;
this.textObject.color.fill = 'white';
}.bind(this),200);
};
var to_menu_button = this.UI.addObject(5,true,'to_menu_button',new Button(100,37,165,45,"TIl MENYEN",true,"white"));
to_menu_button.textObject.color.fill = "white";
to_menu_button.events.onclick = function (e,state) {
this.fill = true;
this.textObject.color.fill = 'black';
setTimeout(function(){
App.stateHandler.change(App.stateHandler.states.MENU);
}.bind(this),200);
};
},
update: function (dt) {
this.UI.update(dt);
},
draw: function () {
this.UI.draw(App.ctxs.UI);
if(this.path.length > 1) {
App.ctxs.foreground.strokeStyle = "white";
App.ctxs.foreground.beginPath();
App.ctxs.foreground.moveTo(this.path[0].x, this.path[0].y);
for (var i = 1; i < this.path.length; i++) {
App.ctxs.foreground.lineTo(this.path[i].x, this.path[i].y);
}
App.ctxs.foreground.stroke();
}
},
reset: function () {}
},
TEST: {
events_last_tick: [],
events: {
},
init: function () {
},
update: function (dt) {
},
draw: function () {
}
}
},
prepare_state_events: function () {
for(var event_type in this.current.events){
if(this.current.events.hasOwnProperty(event_type)){
(function (e_t) {
document[e_t] = function (e) {
this.current.events_last_tick.push({event:e,event_type:e_t});
}.bind(this);
}).call(this,event_type);
}
}
},
remove_state_events: function () {
for(var event_type in this.current.events){
if(this.current.events.hasOwnProperty(event_type)){
document.removeEventListener(event_type,this.current.events[event_type]);
}
}
},
change: function(newState,stateOptions){
if(this.current != null) {
this.remove_state_events();
if(this.current.reset) this.current.reset();
}
this.current = newState;
this.prepare_state_events();
this.current.init(stateOptions || {});
}
}, //Håndterer de ulike tilstandene til applikasjonen (Som nevnt i dokumentasjon)
width: CONST.APP.SIZE.WIDTH,
height: CONST.APP.SIZE.HEIGHT,
loadMaps: function () {
calc.input.file.json("maps.json",function (json) {
CONST.MAPS = JSON.parse(json);
});
},
initCanvases: function () {
for(var name in this.canvases){
if(this.canvases.hasOwnProperty(name)){
if(this.canvases[name] === null) this.canvases[name] = document.getElementById(name);
if(this.canvases[name] instanceof HTMLCanvasElement){
this.canvases[name].width = App.width;
this.canvases[name].height = App.height;
this.ctxs[name] = this.canvases[name].getContext('2d');
}
}
}
},
initAudioContext: function () {
this.audio.ctx = new (window.AudioContext || window.webkitAudioContext)(); //Definerer lyd kontekst. Webkit/blink browsers trenger prefix, Safari fungerer ikke uten window.
},
init: function () {
this.initCanvases();
this.initAudioContext();
this.loadMaps();
this.stateHandler.change(this.stateHandler.states.MENU);
},
handleInput: function () {
if(this.stateHandler.current) {
for (var i = 0; i < this.stateHandler.current.events_last_tick.length; i++) {
var handler = this.stateHandler.current.events[this.stateHandler.current.events_last_tick[i].event_type];
if (handler) handler(this.stateHandler.current.events_last_tick[i].event,this.stateHandler.current);
}
this.stateHandler.current.events_last_tick = [];
}
},
updateCanvasSizes: function () {
for (var name in this.canvases) {
if (this.canvases.hasOwnProperty(name)) {
this.canvases[name].width = App.width;
this.canvases[name].height = App.height;
}
}
},
updateSize: function () {
this.width = window.innerWidth;
this.height = window.innerHeight;
},
update: function () {
this.clock.update();
this.stateHandler.current.update(this.clock.delta);
if(CONST.APP.SIZE.FIT_SCREEN) {
this.updateSize();
this.updateCanvasSizes();
}
},
draw: function () {
for(var name in this.ctxs){
if(this.ctxs.hasOwnProperty(name)) this.ctxs[name].clearRect(0,0,this.ctxs[name].canvas.width,this.ctxs[name].canvas.height);
}
this.stateHandler.current.draw();
},
run: function () {
this.handleInput();
this.update();
this.draw();
this.animationID = requestAnimationFrame(this.run.bind(this));
} //Spilløkken og funksjonen som kaller seg selv inn i "requestAnimationFrame"
};
document.addEventListener('DOMContentLoaded', function () {
App.init();
App.run();
});