Revive old code. Add README and screenshot.

This commit is contained in:
Michael Soukup 2021-08-18 23:14:13 +02:00
commit 88dfb3b20c
8 changed files with 607 additions and 0 deletions

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# AlGoreithm
![Screenshot](screenshot.png "Screenshot")
Prototype for game developed in the "experts in teams" course at NTNU.
Everything about this code sucks, but it does implement some cool orbital
mechanics!
Controls appear to be:
- Left/right arrow keys to move player
- Up/down to aim cannon
- Spacebar (hold and release) to shoot
- Enter to change player.
Shoot and hit each sector until it is completely red/green to win.

568
algoreithm.js Executable file
View File

@ -0,0 +1,568 @@
// 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();

BIN
images/evil.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
images/green.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
images/gun.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

BIN
images/space_bg.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

23
index.html Executable file
View File

@ -0,0 +1,23 @@
<html>
<head>
<title>AlGoreithm</title>
</head>
<body>
<center>
<div id="viewport" style="position: relative;">
<canvas id="layer1" style="position: absolute; border: 1px solid #000; left: 0; top: 0;">
</canvas>
<canvas id="layer2" style="position: absolute; left: 0; top: 0; background-color: transparent;">
</canvas>
<canvas id="layer3" style="position: absolute; left: 0; top: 0; background-color: transparent;">
</canvas>
</div>
</center>
<script src="algoreithm.js"></script>
</body>
</html>

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB