commit 88dfb3b20ce3719f3ef930786cba397baea0316a Author: Michael Soukup Date: Wed Aug 18 23:14:13 2021 +0200 Revive old code. Add README and screenshot. diff --git a/README.md b/README.md new file mode 100644 index 0000000..61f839f --- /dev/null +++ b/README.md @@ -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. diff --git a/algoreithm.js b/algoreithm.js new file mode 100755 index 0000000..86e82a2 --- /dev/null +++ b/algoreithm.js @@ -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 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(); + + diff --git a/images/evil.png b/images/evil.png new file mode 100755 index 0000000..1ce77a4 Binary files /dev/null and b/images/evil.png differ diff --git a/images/green.png b/images/green.png new file mode 100755 index 0000000..f3e8741 Binary files /dev/null and b/images/green.png differ diff --git a/images/gun.png b/images/gun.png new file mode 100755 index 0000000..9dd911c Binary files /dev/null and b/images/gun.png differ diff --git a/images/space_bg.jpg b/images/space_bg.jpg new file mode 100755 index 0000000..fa3a397 Binary files /dev/null and b/images/space_bg.jpg differ diff --git a/index.html b/index.html new file mode 100755 index 0000000..b3a44ec --- /dev/null +++ b/index.html @@ -0,0 +1,23 @@ + + + AlGoreithm + + + +
+
+ + + + + + + + +
+
+ + + + + diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..0e28979 Binary files /dev/null and b/screenshot.png differ