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(false); } } 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); } } }