summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.js767
1 files changed, 767 insertions, 0 deletions
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..a796981
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,767 @@
+var ctx = null;
+var canvas = null;
+
+var player = null;
+
+var now = 0;
+var last = 0;
+var ts = 0;
+
+var camera_offset = 2;
+
+const sprite_scale = 2;
+
+var spritesheet = null;
+
+const sprites = {
+ player: { x: 0, y: 0, w: 16, h: 16 },
+ rock: { x: 0, y: 32, w: 16, h: 16 },
+ hard_rock: { x: 96, y: 32, w: 16, h: 16 },
+ sky: { x: 112, y: 16, w: 16, h: 16 },
+ background: { x: 112, y: 32, w: 16, h: 16 },
+ grass: { x: 128, y: 0, w: 16, h: 16 },
+ diamond: { x: 112, y: 0, w: 16, h: 16 },
+ food_icon: { x: 128, y: 16, w: 16, h: 16 },
+ food: { x: 128, y: 32, w: 16, h: 16 },
+}
+
+const animations = {
+ player_dig_right: {
+ frames: [
+ { x: 0, y: 0, w: 16, h: 16 },
+ { x: 16, y: 0, w: 16, h: 16 },
+ { x: 32, y: 0, w: 16, h: 16 },
+ { x: 48, y: 0, w: 16, h: 16 },
+ { x: 64, y: 0, w: 16, h: 16 },
+ { x: 80, y: 0, w: 16, h: 16 },
+ { x: 96, y: 0, w: 16, h: 16 },
+ { x: 0, y: 0, w: 16, h: 16 },
+ ],
+ speed: 15,
+ loop: false
+ },
+ player_dig_left: {
+ frames: [
+ { x: 0, y: 16, w: 16, h: 16 },
+ { x: 16, y: 16, w: 16, h: 16 },
+ { x: 32, y: 16, w: 16, h: 16 },
+ { x: 48, y: 16, w: 16, h: 16 },
+ { x: 64, y: 16, w: 16, h: 16 },
+ { x: 80, y: 16, w: 16, h: 16 },
+ { x: 96, y: 16, w: 16, h: 16 },
+ { x: 0, y: 16, w: 16, h: 16 },
+ ],
+ speed: 15,
+ loop: false
+ },
+ player_hurt_left: {
+ frames: [
+ { x: 144, y: 32, w: 16, h: 16 },
+ { x: 144, y: 32, w: 16, h: 16 },
+ ],
+ speed: 5,
+ loop: false
+ },
+ player_hurt_right: {
+ frames: [
+ { x: 144, y: 16, w: 16, h: 16 },
+ { x: 144, y: 16, w: 16, h: 16 },
+ ],
+ speed: 5,
+ loop: false
+ },
+ tile_decay: {
+ frames: [
+ { x: 0, y: 32, w: 16, h: 16 },
+ { x: 16, y: 32, w: 16, h: 16 },
+ { x: 32, y: 32, w: 16, h: 16 },
+ { x: 48, y: 32, w: 16, h: 16 },
+ { x: 64, y: 32, w: 16, h: 16 },
+ { x: 80, y: 32, w: 16, h: 16 },
+ ],
+ speed: 20,
+ loop: false
+ },
+ fire: {
+ frames: [
+ { x: 144, y: 0, w: 16, h: 16 },
+ { x: 160, y: 0, w: 16, h: 16 },
+ { x: 176, y: 0, w: 16, h: 16 },
+ ],
+ speed: 10,
+ loop: true
+ }
+}
+
+const spawn_rates = {
+ diamond: 10
+}
+
+var characters = {};
+
+var animated_tiles = [];
+var objs = [];
+
+var map = null;
+var map_width = 0;
+var map_height = 0;
+var map_value = 0;
+const map_bottom_size = 6;
+const map_grid_size = 16;
+
+var collected = 0;
+var gem_count = 0;
+
+var score = 0;
+var level = 0;
+var food = 100;
+const hunger = 1;
+
+var name_buffer = "";
+
+var win = null;
+var next_level_timer = 0;
+
+var state = "menu";
+
+function random_int(min, max) {
+ return Math.floor(Math.random() * (max - min)) + min;
+}
+
+function random_chance(chance) {
+ if (chance > 100) {
+ chance = 100;
+ }
+
+ return random_int(0, 100) < chance;
+}
+
+function remove_from_array(arr, value) {
+ var idx = arr.indexOf(value);
+ if (idx !== -1) {
+ arr.splice(idx, 1);
+ }
+}
+
+function turn() {
+ objs.forEach(function(value) {
+ if (value.turn) {
+ value.turn();
+ }
+ });
+
+ for (let i = 0; i < level; i++) {
+ let x = random_int(1, map_width - 1);
+ let y = random_int(1, map_height - 1);
+
+ if (map[x][y] == 0 && obj_at(x, y) == null) {
+ objs.push(new Fire(x, y));
+ }
+ }
+}
+
+function obj_at(x, y) {
+ for (let i = 0; i < objs.length; i++) {
+ if (objs[i].go.x == x && objs[i].go.y == y) {
+ return objs[i];
+ }
+ }
+
+ return null;
+}
+
+function apply_gravity(obj) {
+ while (map[obj.go.x][obj.go.y + 1] == 0) {
+ let other_obj = false;
+
+ for (let i = 0; i < objs.length; i++) {
+ let value = objs[i];
+ if (obj.go.x == value.go.x && obj.go.y + 1 == value.go.y) {
+ other_obj = true;
+ break;
+ }
+ }
+
+ if (other_obj) {
+ break;
+ }
+
+ obj.go.y += 1;
+ }
+}
+
+function GameObject(sprite) {
+ this.x = 0;
+ this.y = 0;
+
+ this.rect = sprite;
+
+ this.animation = null;
+ this.current_frame = 0;
+ this.timer = 0;
+
+ this.draw = function() {
+ ctx.drawImage(spritesheet, this.rect.x, this.rect.y, this.rect.w,
+ this.rect.h, this.x * map_grid_size * sprite_scale, (this.y + camera_offset) * map_grid_size * sprite_scale,
+ this.rect.w * sprite_scale, this.rect.h * sprite_scale);
+ }
+
+ this.update = function() {
+ if (this.animation != null) {
+ this.timer += ts * this.animation.speed;
+
+ if (this.timer >= 1.0) {
+ this.current_frame++;
+ this.timer = 0;
+
+ if (this.current_frame >= this.animation.frames.length) {
+ if (this.animation.loop) {
+ this.current_frame = 0;
+ } else {
+ this.animation = null;
+ }
+ }
+
+ if (this.animation != null) {
+ this.rect = this.animation.frames[this.current_frame];
+ }
+ }
+ }
+ }
+
+ this.animate = function(animation) {
+ this.animation = animation;
+ this.current_frame = 0;
+ }
+}
+
+function DecayTile() {
+ this.go = new GameObject(sprites.rock);
+ this.go.animate(animations.tile_decay);
+ this.go.type = "fire";
+
+ this.update = function() {
+ this.go.update();
+
+ if (this.go.animation == null) {
+ map[this.go.x][this.go.y] = 0;
+ remove_from_array(animated_tiles, this);
+ }
+ }
+}
+
+function Fire(px, py) {
+ this.go = new GameObject(animations.fire.frames[0]);
+ this.go.animate(animations.fire);
+
+ this.go.x = px;
+ this.go.y = py;
+
+ this.timer = 0;
+ this.spawned = false;
+
+ this.update = function() {
+ this.go.update();
+
+ if (this.go.x == player.go.x && this.go.y == player.go.y) {
+ food -= 10;
+ if (player.face == "left") {
+ player.go.animate(animations.player_hurt_left);
+ } else {
+ player.go.animate(animations.player_hurt_right);
+ }
+ remove_from_array(objs, this);
+ }
+
+ if (!this.spawned && this.timer > 3) {
+ for (let x = this.go.x - 1; x < this.go.x + 2; x++) {
+ for (let y = this.go.y - 1; y < this.go.y + 2; y++) {
+ if (random_chance(40) && (x != this.go.x || y != this.go.y) && map[x][y] == 0 && obj_at(x, y) == null) {
+ objs.push(new Fire(x, y));
+ this.timer = 0;
+ this.spawned = true;
+ }
+ }
+ }
+
+ this.timer = 0;
+ } else if (this.spawned && this.timer > random_int(0, 4)) {
+ remove_from_array(objs, this);
+ }
+
+ apply_gravity(this);
+ }
+
+ this.draw = function() {
+ this.go.draw();
+ }
+
+ this.turn = function() {
+ this.timer++;
+ }
+}
+
+function Gem(x, y, sprite, is_food) {
+ this.go = new GameObject(sprite);
+
+ this.go.x = x;
+ this.go.y = y;
+
+ this.value = random_int(10, 25);
+
+ this.update = function() {
+ this.go.update();
+
+ if (this.go.x == player.go.x && this.go.y == player.go.y) {
+ if (!is_food) {
+ score += this.value;
+ collected++;
+ } else {
+ food += this.value;
+ }
+
+ remove_from_array(objs, this);
+ }
+
+ apply_gravity(this);
+ }
+
+ this.draw = function() {
+ this.go.draw();
+ }
+}
+
+function destroy_tile(x, y) {
+ if (map[x][y] != 0) {
+ map[x][y] = 200;
+
+ let obj = new DecayTile();
+ obj.go.x = x;
+ obj.go.y = y;
+ obj.go.animate(animations.tile_decay);
+
+ animated_tiles[animated_tiles.length + 1] = obj;
+ }
+}
+
+function Player() {
+ this.go = new GameObject(sprites.player);
+
+ this.go.x = 11;
+
+ this.next_move_x = null;
+ this.next_move_y = null;
+
+ this.face = "right";
+
+ this.draw = function() {
+ this.go.draw();
+ }
+
+ this.update = function() {
+ this.go.update();
+
+ while (map[this.go.x][this.go.y + 1] == 0) {
+ this.go.y += 1;
+
+ if (this.go.y > 10) {
+ camera_offset -= 1;
+ }
+ }
+
+ if (this.go.animation == null) {
+ if (this.next_move_x != null) {
+ this.go.x += this.next_move_x;
+ this.next_move_x = null;
+ }
+
+ if (this.next_move_y != null) {
+ this.go.y += this.next_move_y;
+
+ if (this.go.y > 10) {
+ camera_offset -= 1;
+ }
+ this.next_move_y = null;
+ }
+ }
+
+ if (food <= 0) {
+ win = false;
+ }
+
+ if (collected == gem_count) {
+ win = true;
+ }
+
+ if (this.go.y == map_height - 1) {
+ win = true;
+ }
+ }
+
+ this.update_movement = function(key) {
+ if (this.go.animation == null) {
+ if (key == 37) {
+ this.next_move_x = -1;
+ this.go.rect = animations.player_dig_left.frames[0];
+ this.face = "left";
+
+ let tile = map[this.go.x + this.next_move_x][this.go.y];
+
+ if (tile == 1) {
+ destroy_tile(this.go.x + this.next_move_x, this.go.y);
+ this.go.animate(animations.player_dig_left);
+ food -= hunger;
+ } else if (tile == 2) {
+ this.next_move_x = null;
+ }
+
+ turn();
+ } else if (key == 39) {
+ this.next_move_x = 1;
+ this.go.rect = animations.player_dig_right.frames[0];
+ this.face = "right";
+
+ let tile = map[this.go.x + this.next_move_x][this.go.y];
+
+ if (tile == 1) {
+ destroy_tile(this.go.x + this.next_move_x, this.go.y);
+ this.go.animate(animations.player_dig_right);
+ food -= hunger;
+ } else if (tile == 2) {
+ this.next_move_x = null;
+ }
+
+ turn();
+ } else if (key == 40) {
+ if (this.face == "right") {
+ this.go.rect = animations.player_dig_right.frames[0];
+ this.next_move_x = 1;
+ } else {
+ this.go.rect = animations.player_dig_left.frames[0];
+ this.next_move_x = -1;
+ }
+
+ this.next_move_y = 1;
+
+ if (map[this.go.x + this.next_move_x][this.go.y + this.next_move_y] == 1 ||
+ map[this.go.x + this.next_move_x][this.go.y] == 1) {
+ if (this.face == "right") {
+ this.go.animate(animations.player_dig_right);
+ food -= hunger;
+ } else {
+ this.go.animate(animations.player_dig_left);
+ food -= hunger;
+ }
+
+ if (map[this.go.x + this.next_move_x][this.go.y + this.next_move_y] == 2) {
+ this.next_move_y = null;
+ }
+
+ destroy_tile(this.go.x + this.next_move_x, this.go.y);
+ destroy_tile(this.go.x + this.next_move_x, this.go.y + this.next_move_y);
+ } else {
+ this.next_move_x = null;
+ this.next_move_y = null;
+ }
+
+ turn();
+ }
+ }
+ }
+}
+
+function draw_text(start_x, start_y, text) {
+ let x = start_x;
+ let y = start_y;
+
+ for (let i = 0; i < text.length; i++) {
+ let c = text.charAt(i);
+
+ let rect = characters[c];
+
+ if (rect) {
+ ctx.drawImage(spritesheet, rect.x, rect.y, rect.w,
+ rect.h, x, y,
+ rect.w * sprite_scale, rect.h * sprite_scale);
+ }
+
+ x += 16 * sprite_scale;
+ }
+}
+
+function on_init() {
+ window.addEventListener("keydown", on_key_down)
+
+ canvas = document.createElement("canvas");
+ canvas.width = 800;
+ canvas.height = 600;
+
+ ctx = canvas.getContext("2d");
+ ctx.imageSmoothingEnabled= false
+
+ document.body.insertBefore(canvas, document.body.childNodes[0]);
+ let interval = setInterval(on_update, 20);
+
+ spritesheet = new Image();
+ spritesheet.src = "res/sprites.png"
+
+ now = performance.now();
+ last = performance.now();
+
+ let char_order = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
+ let cx = 0;
+ let cy = 0;
+ for (let i = 0; i < char_order.length; i++) {
+ let c = char_order.charAt(i);
+
+ characters[c] = { x: cx, y: cy + 48, w: 16, h: 16 };
+
+ cx += 16;
+
+ if (cx > 240) {
+ cx = 0;
+ cy += 16;
+ }
+ }
+
+ init_game(true);
+}
+
+function init_game(restart) {
+ if (restart) {
+ score = -100;
+ level = 0;
+ food = 100;
+ }
+
+ score += 100;
+
+ level++;
+
+ win = null;
+
+ collected = 0;
+
+ map = null;
+ next_level_timer = 0;
+
+ animated_tiles.length = 0;
+ objs.length = 0;
+
+ camera_offset = 2;
+
+ player = new Player();
+
+ map_width = Math.round((canvas.width / (sprites.rock.w * sprite_scale)));
+ map_height = Math.round((canvas.height / (sprites.rock.h * sprite_scale))) * 2;
+
+ map = new Array(map_width);
+ for (let i = 0; i < map_width; i++) {
+ map[i] = new Array(map_height);
+ }
+
+ for (let y = 0; y < map_height; y++) {
+ for (let x = 0; x < map_width; x++) {
+ if (random_chance(1)) {
+ map[x][y] = 0;
+ } else {
+ map[x][y] = 1;
+ }
+ }
+ }
+
+ /* Spawn gems */
+ gem_count = random_int(10, 20);
+ for (let i = 0; i < gem_count; i++) {
+ let x = random_int(1, map_width - 1);
+ let y = random_int(1, map_height - 1);
+
+ map[x][y] = 0;
+
+ objs.push(new Gem(x, y, sprites.diamond, false));
+ }
+
+ /* Spawn fire */
+ let fire_count = random_int(5, 15);
+ for (let i = 0; i < fire_count; i++) {
+ let x = random_int(1, map_width - 1);
+ let y = random_int(1, map_height - 1);
+
+ map[x][y] = 0;
+
+ objs.push(new Fire(x, y));
+ }
+
+ /* Spawn food */
+ let food_count = random_int(2, 10);
+ for (let i = 0; i < food_count; i++) {
+ let x = random_int(1, map_width - 1);
+ let y = random_int(1, map_height - 1);
+
+ map[x][y] = 0;
+
+ let obj = new Gem(x, y, sprites.food, true);
+ obj.value = random_int(10, 15);
+ objs.push(obj);
+ }
+
+ for (let y = 0; y < map_height; y++) {
+ map[0][y] = 2;
+ }
+
+ for (let y = 0; y < map_height; y++) {
+ map[map_width - 1][y] = 2;
+ }
+
+ for (let x = 0; x < map_width; x++) {
+ for (let y = map_height; y < map_height + map_bottom_size; y++) {
+ map[x][y] = 2;
+ }
+ }
+
+ map[10][0] = 0;
+ map[11][0] = 0;
+ map[12][0] = 0;
+ map[13][0] = 0;
+}
+
+function on_update() {
+ if (state == "game") {
+ if (win == null) {
+ player.update();
+
+ animated_tiles.forEach(function(value) {
+ value.update();
+ });
+
+ objs.forEach(function(value) {
+ value.update();
+ });
+ }
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ /* Draw the sky */
+ for (let y = 0; y < 2; y++) {
+ for (let x = 0; x < map_width; x++) {
+ let rect = sprites.sky;
+
+ ctx.drawImage(spritesheet, rect.x, rect.y, rect.w, rect.h,
+ x * map_grid_size * sprite_scale, (y + camera_offset - 2) * map_grid_size * sprite_scale, rect.w * sprite_scale, rect.h * sprite_scale);
+ }
+ }
+
+ /* Draw the grass */
+ for (let x = 0; x < map_width; x++) {
+ let y = 1;
+ let rect = sprites.grass;
+
+ if (map[x][0] != 0) {
+ ctx.drawImage(spritesheet, rect.x, rect.y, rect.w, rect.h,
+ x * map_grid_size * sprite_scale, (y + camera_offset - 2) * map_grid_size * sprite_scale, rect.w * sprite_scale, rect.h * sprite_scale);
+ }
+ }
+
+ /* Draw the background */
+ for (let y = 2; y < map_height + 2; y++) {
+ for (let x = 0; x < map_width; x++) {
+ let rect = sprites.background;
+
+ ctx.drawImage(spritesheet, rect.x, rect.y, rect.w, rect.h,
+ x * map_grid_size * sprite_scale, (y + camera_offset - 2) * map_grid_size * sprite_scale, rect.w * sprite_scale, rect.h * sprite_scale);
+ }
+ }
+
+ /* Draw the map */
+ for (let y = 0; y < map_height + map_bottom_size; y++) {
+ for (let x = 0; x < map_width; x++) {
+ let rect = null;
+
+ if (map[x][y] == 1) {
+ rect = sprites.rock;
+ } else if (map[x][y] == 2) {
+ rect = sprites.hard_rock;
+ }
+
+ if (rect != null) {
+ ctx.drawImage(spritesheet, rect.x, rect.y, rect.w, rect.h,
+ x * map_grid_size * sprite_scale, (y + camera_offset) * map_grid_size * sprite_scale, rect.w * sprite_scale, rect.h * sprite_scale);
+ }
+ }
+ }
+
+ animated_tiles.forEach(function(value) {
+ value.go.draw();
+ });
+
+ objs.forEach(function(value) {
+ value.go.draw();
+ });
+
+ player.draw();
+
+ let score_text = `Score: ${score}`;
+ let level_text = `Level: ${level}`;
+ draw_text(canvas.width - score_text.length * 16 * sprite_scale, 0, score_text);
+ draw_text(canvas.width - level_text.length * 16 * sprite_scale, 16 * sprite_scale, level_text);
+
+ let food_text = `${Math.round(food)}%`;
+ let r = sprites.food_icon;
+ ctx.drawImage(spritesheet, r.x, r.y, r.w, r.h,
+ 0, 0, r.w * sprite_scale, r.h * sprite_scale);
+ draw_text(16 * sprite_scale, 0, food_text);
+
+ if (win != null) {
+ let win_text = "Game Over!";
+
+ if (win) {
+ win_text = "Level Up!";
+ }
+
+ draw_text((canvas.width / 2) - ((win_text.length * 16 * sprite_scale) / 2),
+ (canvas.height / 2) - ((16 * sprite_scale) / 2), win_text);
+
+ next_level_timer += ts;
+
+ if (win && next_level_timer >= 3) {
+ init_game(true);
+ }
+ }
+
+ now = performance.now();
+ ts = (now - last) / 1000;
+ last = now;
+ } else if (state == "menu") {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ let text = "[S]tart";
+ draw_text((canvas.width / 2) - ((text.length * 16 * sprite_scale) / 2),
+ ((canvas.height / 2) - ((16 * sprite_scale) / 2)) - 16 * sprite_scale, text);
+ text = "MINI MINER";
+ draw_text((canvas.width / 2) - ((text.length * 16 * sprite_scale) / 2),
+ (canvas.height / 2) - ((16 * sprite_scale) / 2) - 16 * 3 * sprite_scale, text);
+ }
+}
+
+function on_key_down(e) {
+ e.preventDefault();
+
+ if (e.keyCode == 75) { /* K */
+ food = 0;
+ }
+
+ if (e.keyCode == 80) { /* K */
+ score += 10;
+ }
+
+ if (state == "menu") {
+ if (e.keyCode == 83) { /* S */
+ state = "game";
+ init_game(true);
+ }
+ } else if (state == "game") {
+ if (win == null) {
+ player.update_movement(e.keyCode);
+ }
+ }
+}