algorerithm/algoreithm.js

569 lines
14 KiB
JavaScript
Executable File

// Set dimensions
var _height, _width, _margin = 20, _aspect = 16/9, _minw = 640, _minh = _minw/_aspect;
if (window.innerWidth > window.innerHeight) {
_height = Math.max(_minh, window.innerHeight - 2*_margin);
_width = _height*_aspect;
}
else {
_width = Math.max(_minw, window.innerWidth - 2*_margin);
_height = _width/_aspect;
}
// Game constants
var constants = {
SCREEN_DIM: [_width, _height],
SCREEN_CENTER: [_width/2, _height/2],
BG_COLOR: '#223322',
WORLD_RADIUS: _height/4,
WORLD_POS: [_width/2, _height/2],
WORLD_CONTOUR: 5,
WORLD_CONTOUR_COL: '#003300',
ZERO_POTENSIAL_RADIUS: _height*0.6,
PLAYER_SPEED: Math.PI/20, // rad/s
BARREL_SPEED: Math.PI/10,
BARREL_MAX: Math.PI/3,
BARREL_MIN: -Math.PI/3,
MISSILE_RADIUS: 5,
MISSILE_LOAD_SPEED: 100
};
// Game states
var state = {
LOADING: 1,
PLAY: 2,
P_CHANGE: 3,
PRE_FIRE: 4,
FIRE: 5
};
// Event manager
var keys_down = {};
addEventListener('keydown', function (e) {
keys_down[e.keyCode] = true;
}, false);
addEventListener('keyup', function (e) {
delete keys_down[e.keyCode];
}, false);
// Assets. Preload
var assets_loaded = 0;
function assetLoadCB() {
assets_loaded++;
}
var assets = {
space_bg: new Image(),
evil_player: new Image(),
green_player: new Image(),
evil_barrel: new Image(),
green_barrel: new Image()
};
assets.space_bg.src = 'images/space_bg.jpg'; assets.space_bg.onload = function() {assetLoadCB();};
assets.evil_player.src = 'images/evil.png'; assets.evil_player.onload = function() {assetLoadCB();};
assets.green_player.src = 'images/green.png'; assets.green_player.onload = function() {assetLoadCB();};
assets.evil_barrel.src = 'images/gun.png'; assets.evil_barrel.onload = function() {assetLoadCB();};
assets.green_barrel.src = 'images/gun.png'; assets.green_barrel.onload = function() {assetLoadCB();};
// Timer
function Timer() {}
Timer.prototype.isset = function() {
return this.start !== 'undefined' && this.interval !== 'undefined';
}
Timer.prototype.set = function(msec) {
this.start = Date.now();
this.interval = msec;
}
Timer.prototype.finished = function() {
return ((Date.now() - this.start) > this.interval)
}
function Polar(origo, r0, theta0) {
this.origo = origo;
this.r = r0;
this.theta = theta0;
}
Polar.prototype.euler = function(rp, thetap) {
var add_r = (typeof rp === 'undefined') ? 0:rp,
add_theta = (typeof thetap === 'undefined') ? 0:thetap;
return [this.origo[0] + (this.r + add_r)*Math.cos(this.theta + add_theta), this.origo[1] - (this.r + add_r)*Math.sin(this.theta + add_theta)]
}
function Missile(ctx, polar0, color) {
this.fired = false;
this.ctx = ctx;
this.polar = polar0;
this.polar_v = new Polar(constants.WORLD_POS, 0, 0);
this.v0 = 0;
this.color = color;
}
Missile.prototype.fire = function(gun_theta) {
this.polar_v.r = Math.cos(gun_theta) * this.v0;
this.polar_v.theta = Math.sin(gun_theta) * (Math.PI/180) * this.v0;
console.log(this.polar_v.r + ' ' + this.polar_v.theta);
this.fired = true;
}
Missile.prototype.outOfBounds = function() {
return (this.euler[0] > constants.SCREEN_DIM[0] || this.euler[0] < 0 || this.euler[1] > constants.SCREEN_DIM[1] || this.euler[1] < 0);
}
Missile.prototype.checkCollision = function() {
return (this.polar.r < constants.WORLD_RADIUS);
}
function getGravity(r) {
var dr = r - (constants.WORLD_RADIUS);
return -10000000/(r*r); // Empirical
}
Missile.prototype.update = function(delta) {
this.polar_v.r += getGravity(this.polar.r) * delta;
this.polar.r += this.polar_v.r * delta;
this.polar.theta += this.polar_v.theta * delta;
this.euler = this.polar.euler();
}
Missile.prototype.draw = function() {
drawFilledCircle(this.ctx, this.euler, constants.MISSILE_RADIUS, 0, 2*Math.PI, this.color, 1, 'black');
}
function Player(ctx, is_evil, polar) {
this.ctx = ctx;
this.polar = polar;
this.barrel_theta = 0;
// Load images
this.image = (is_evil) ? assets.evil_player:assets.green_player;
this.barrel_img = (is_evil) ? assets.evil_barrel:assets.green_barrel;
//center
//console.log(this.image.height);
this.polar.theta += this.image.height/(4*Math.PI*constants.WORLD_RADIUS); // simplify, use arcsin.
this.polar.r += this.image.width/2;
this.euler = this.polar.euler();
}
Player.prototype.moveBarrel = function(modifier, delta) {
if (!((this.barrel_theta > constants.BARREL_MAX && modifier == 1) ||
(this.barrel_theta < constants.BARREL_MIN && modifier == -1))) {
this.barrel_theta += modifier*constants.BARREL_SPEED*delta;
}
}
Player.prototype.move = function(modifier, delta) {
this.polar.theta += modifier*constants.PLAYER_SPEED*delta;
this.euler = this.polar.euler();
}
Player.prototype.drawBarrel = function() {
drawRotatedImage(this.ctx, this.barrel_img, this.polar.euler(this.barrel_img.width + 5), [this.barrel_img.width, this.barrel_img.height], this.polar.theta + this.barrel_theta);
}
Player.prototype.draw = function() {
drawRotatedImage(this.ctx, this.image, this.euler, [this.image.width, this.image.height], this.polar.theta);
this.drawBarrel();
}
var sector_cols = {
'land': '#8B4513',
'water': '#104E8B',
'ice': '#E0FFFF',
'forest': '#228B22'
};
function Sector(ctx, type, theta0, theta) {
this.ctx = ctx;
this.type = type;
this.theta0 = theta0;
this.theta = theta;
this.health = 0; //[-1, 1]
}
Sector.prototype.hit = function(h) {
if (Math.abs(this.health) === 1 && this.type !== 'ice') {
return; //sector is locked
}
this.health += h;
this.health = (this.health < -1) ? -1:this.health;
this.health = (this.health > 1) ? 1:this.health;
}
Sector.prototype.draw = function() {
drawFilledCone(
this.ctx,
constants.WORLD_POS,
constants.WORLD_RADIUS,
this.theta0, this.theta,
sector_cols[this.type]
);
if (this.health !== 0) {
drawFilledCone(
this.ctx,
constants.WORLD_POS,
constants.WORLD_RADIUS,
this.theta0, this.theta,
(this.health < 0) ? ('rgba(255, 0, 0, ' + (-this.health) + ')'):('rgba(0, 255, 0, ' + (this.health) + ')')
);
}
}
function Game() {
console.log('state LOADING');
this.state = state.LOADING;
// Initialize canvases
this.bg_canvas = document.getElementById('layer1');
this.g_canvas = document.getElementById('layer2');
this.hud_canvas = document.getElementById('layer3');
this.bg_canvas.width = this.g_canvas.width = this.hud_canvas.width = constants.SCREEN_DIM[0];
this.bg_canvas.height = this.g_canvas.height = this.hud_canvas.height = constants.SCREEN_DIM[1];
this.bg_ctx = this.bg_canvas.getContext('2d'); // BG and world
this.draw_bg = true; // Save some resources. Background is pretty static.
this.g_ctx = this.g_canvas.getContext('2d'); // Game layer. Always draw
this.hud_ctx = this.hud_canvas.getContext('2d'); // Menu and stats
this.draw_hud = true; // Also pretty static
}
function randRange(lower, upper) {
return lower + (upper - lower)*Math.random();
}
Game.prototype.init = function() {
// Instantiate players
this.players = [
new Player(this.g_ctx, true, new Polar(constants.WORLD_POS, constants.WORLD_RADIUS, Math.PI/2)),
new Player(this.g_ctx, false, new Polar(constants.WORLD_POS, constants.WORLD_RADIUS, Math.PI/2))
];
this.curr_p = 0; // randomize later
this.p_change = new Timer();
// Init sectors
var sec_borders = [],
slack = 2*Math.PI/20,
ice_width = 2*Math.PI/20,
sec_width = 2*Math.PI/7;
sec_borders.push(0);
sec_borders.push(randRange(sec_width - slack, sec_width + slack));
sec_borders.push(randRange(2*sec_width - slack, 2*sec_width + slack));
sec_borders.push(randRange(3*sec_width - slack, 3*sec_width + slack));
sec_borders.push(randRange(4*sec_width - slack, 4*sec_width + slack));
sec_borders.push(randRange(5*sec_width - slack, 5*sec_width + slack));
sec_borders.push(sec_borders[5] + ice_width);
sec_borders.push(2*Math.PI);
this.sectors = [
new Sector(this.bg_ctx, 'land', sec_borders[0], sec_borders[1]),
new Sector(this.bg_ctx, 'water', sec_borders[1], sec_borders[2]),
new Sector(this.bg_ctx, 'land', sec_borders[2], sec_borders[3]),
new Sector(this.bg_ctx, 'forest', sec_borders[3], sec_borders[4]),
new Sector(this.bg_ctx, 'water', sec_borders[4], sec_borders[5]),
new Sector(this.bg_ctx, 'ice', sec_borders[5], sec_borders[6]),
new Sector(this.bg_ctx, 'forest', sec_borders[6], sec_borders[7])
];
}
Game.prototype.loadAssets = function() {
this.g_ctx.save();
this.g_ctx.font = '20px Arial';
this.g_ctx.fillStyle = 'red';
this.g_ctx.fillText('Loading...', constants.SCREEN_CENTER[0] - 40, constants.SCREEN_DIM[1] - 40);
this.g_ctx.restore();
if (assets_loaded < Object.keys(assets).length) {
return state.LOADING;
}
else {
console.log('state PLAY');
this.init();
return state.PLAY;
}
}
Game.prototype.drawWorld = function() {
drawFilledCircle(
this.bg_ctx,
constants.WORLD_POS,
constants.ZERO_POTENSIAL_RADIUS,
0, 2*Math.PI,
'rgba(255, 255, 255, 0.1)'
);
drawFilledCircle(
this.bg_ctx,
constants.WORLD_POS,
constants.WORLD_RADIUS,
0, 2*Math.PI,
'white'
);
}
Game.prototype.drawBGLayer = function() {
if (this.draw_bg) {
this.bg_ctx.clearRect(0, 0, constants.SCREEN_DIM[0], constants.SCREEN_DIM[1]);
var x = y = 0;
while (x < constants.SCREEN_DIM[0]) {
while (y < constants.SCREEN_DIM[1]) {
this.bg_ctx.drawImage(assets.space_bg, x, y);
y += assets.space_bg.height;
}
y = 0
x += assets.space_bg.width;
}
this.drawWorld();
this.draw_bg = false;
}
}
Game.prototype.drawGLayer = function() {
this.g_ctx.clearRect(0, 0, constants.SCREEN_DIM[0], constants.SCREEN_DIM[1]);
// Draw sectors
for (var i=0; i<this.sectors.length; i++) {
this.sectors[i].draw();
}
// Draw player
this.players[this.curr_p].draw();
}
Game.prototype.changePlayer = function() {
if (this.p_change.finished()) {
this.curr_p = (this.curr_p === 0) ? 1:0;
console.log('state PLAY');
this.state = state.PLAY;
}
else {
var next = (this.curr_p === 0) ? 2:1;
this.g_ctx.clearRect(0, 0, constants.SCREEN_DIM[0], constants.SCREEN_DIM[1]);
this.g_ctx.save();
this.g_ctx.font = '20px Arial';
this.g_ctx.fillStyle = (next === 1) ? 'red':'green';
this.g_ctx.fillText(('Player ' + next), constants.SCREEN_CENTER[0] - 40, constants.SCREEN_DIM[1] - 40);
this.g_ctx.restore();
}
}
Game.prototype.preFire = function(delta) {
if (keys_down[32]) {
this.missile.v0 += constants.MISSILE_LOAD_SPEED * delta;
}
else {
this.missile.fire(this.players[this.curr_p].barrel_theta);
console.log('state FIRE');
this.state = state.FIRE;
}
}
Game.prototype.sectorHit = function() {
var i = 0,
_theta = this.missile.polar.theta,
_modifier = (this.curr_p === 0) ? -1:1;
while (_theta < 0) {
_theta += 2*Math.PI;
}
while (_theta > 2*Math.PI) {
_theta -= 2*Math.PI;
}
while (_theta > this.sectors[i].theta) {
i++;
}
console.log('Hit sector ' + i + ', theta = [' + this.sectors[i].theta0 + ',' + this.sectors[i].theta);
console.log('Missile theta ' + _theta);
this.sectors[i].hit(randRange(0.2, 0.8) * _modifier);
//this.draw_bg = true;
}
Game.prototype.missileFire = function(delta) {
this.missile.update(delta);
if (this.missile.checkCollision()) {
this.sectorHit();
console.log('state PLAY');
this.state = state.PLAY;
}
else if (this.missile.outOfBounds()) {
console.log('state PLAY');
this.state = state.PLAY;
}
else {
this.missile.draw();
}
}
Game.prototype.playEventHandler = function(delta) {
//space
if (keys_down[32]) {
this.missile = new Missile(
this.g_ctx,
new Polar(constants.WORLD_POS, this.players[this.curr_p].polar.r + this.players[this.curr_p].image.width, this.players[this.curr_p].polar.theta),
(this.curr_p === 0) ? 'red':'green');
console.log('state PRE_FIRE');
this.state = state.PRE_FIRE;
}
//enter
if (keys_down[13]) {
this.p_change.set(1000);
console.log('state P_CHANGE');
this.state = state.P_CHANGE;
}
//left
if (keys_down[37]) {
this.players[this.curr_p].move(1, delta);
}
//right
if (keys_down[39]) {
this.players[this.curr_p].move(-1, delta);
}
//down
if (keys_down[40]) {
this.players[this.curr_p].moveBarrel(1, delta);
}
//up
if (keys_down[38]) {
this.players[this.curr_p].moveBarrel(-1, delta);
}
}
Game.prototype.mainLoop = function() {
var now = Date.now(),
delta = (now - this.then)/1000;
switch (this.state) {
case state.LOADING:
this.state = this.loadAssets();
break;
case state.PLAY:
this.playEventHandler(delta);
// more logic
this.drawBGLayer();
this.drawGLayer();
break;
case state.P_CHANGE:
this.drawBGLayer();
this.changePlayer();
break;
case state.PRE_FIRE:
this.drawBGLayer();
this.drawGLayer();
this.preFire(delta);
break;
case state.FIRE:
this.drawBGLayer();
this.drawGLayer();
this.missileFire(delta);
break;
default:
this.drawBGLayer();
}
this.g_ctx.fillText('FPS: ' + parseInt(1/delta), 10, 10); // Show FPS
this.then = now;
}
Game.prototype.run = function() {
//game start
this.drawWorld();
var self = this; // Alias this. stupid setInterval
this.then = Date.now();
setInterval(function() {
self.mainLoop();
}, 1);
}
/*
* Helpers
* */
function drawColorRect(ctx, pos, dims, col) {
ctx.save();
ctx.fillStyle = col;
ctx.beginPath();
ctx.rect(pos[0], pos[1], dims[0], dims[1]);
ctx.closePath();
ctx.fill();
ctx.restore();
}
function drawFilledCone(ctx, pos, r, theta0, theta, fillcol, linewidth, linecol) {
ctx.save();
ctx.beginPath();
ctx.arc(pos[0], pos[1], r, -theta, -theta0, false);
ctx.lineTo(pos[0], pos[1]);
ctx.fillStyle = fillcol;
ctx.fill();
if (linewidth !== 'undefined') {
ctx.lineWidth = linewidth;
ctx.strokeStyle = (linecol === 'undefined') ? 'white':linecol;
ctx.stroke();
}
ctx.restore();
}
function drawFilledCircle(ctx, pos, r, theta0, theta, fillcol, linewidth, linecol) {
ctx.save();
ctx.beginPath();
ctx.arc(pos[0], pos[1], r, -theta, -theta0, false);
ctx.fillStyle = fillcol;
ctx.fill();
if (linewidth !== 'undefined') {
ctx.lineWidth = linewidth;
ctx.strokeStyle = (linecol === 'undefined') ? 'white':linecol;
ctx.stroke();
}
ctx.restore();
}
function drawRotatedImage(ctx, image, origo, dims, theta) {
ctx.save();
ctx.translate(origo[0], origo[1]);
ctx.rotate(-theta);
ctx.drawImage(image, -dims[0]/2, -dims[1]/2);
ctx.restore();
}
var game = new Game();
game.run();