diff --git a/js/defs.js b/js/defs.js new file mode 100644 index 0000000..f551f89 --- /dev/null +++ b/js/defs.js @@ -0,0 +1,26 @@ +export const DIRECTIONS = { + north: { + movement: [0, -1], + left: 'west', + right: 'east', + opposite: 'south', + }, + south: { + movement: [0, 1], + left: 'east', + right: 'west', + opposite: 'north', + }, + west: { + movement: [-1, 0], + left: 'south', + right: 'north', + opposite: 'east', + }, + east: { + movement: [1, 0], + left: 'north', + right: 'south', + opposite: 'west', + }, +}; diff --git a/js/main.js b/js/main.js index 4cbebf0..5e7c36b 100644 --- a/js/main.js +++ b/js/main.js @@ -5,6 +5,7 @@ import * as dat from './format-dat.js'; import * as format_util from './format-util.js'; import TILE_TYPES from './tiletypes.js'; import { Tileset, CC2_TILESET_LAYOUT, TILE_WORLD_TILESET_LAYOUT } from './tileset.js'; +import { DIRECTIONS } from './defs.js'; function mk(tag_selector, ...children) { let [tag, ...classes] = tag_selector.split('.'); @@ -67,33 +68,6 @@ async function fetch(url) { const PAGE_TITLE = "Lexy's Labyrinth"; -const DIRECTIONS = { - north: { - movement: [0, -1], - left: 'west', - right: 'east', - opposite: 'south', - }, - south: { - movement: [0, 1], - left: 'east', - right: 'west', - opposite: 'north', - }, - west: { - movement: [-1, 0], - left: 'south', - right: 'north', - opposite: 'east', - }, - east: { - movement: [1, 0], - left: 'north', - right: 'south', - opposite: 'west', - }, -}; - class Tile { constructor(type, x, y, direction = 'south') { this.type = type; @@ -231,16 +205,16 @@ class Level { this.width = stored_level.size_x; this.height = stored_level.size_y; this.restart(); + } + restart() { // playing: normal play // success: has been won // failure: died // note that pausing is NOT handled here, but by whatever's driving our // event loop! this.state = 'playing'; - } - restart() { this.cells = []; this.player = null; this.actors = []; @@ -371,6 +345,10 @@ class Level { let d = DIRECTIONS[actor.direction]; direction_preference = [actor.direction, d.opposite]; } + else if (actor.type.movement_mode === 'forward') { + // blue tank behavior: keep moving forward + direction_preference = [actor.direction]; + } if (! direction_preference) continue; @@ -531,6 +509,13 @@ class Level { else if (tile.type.on_arrive) { tile.type.on_arrive(tile, this, actor); } + + if ((actor.type.is_player && tile.type.is_monster) || + (actor.type.is_monster && tile.type.is_player)) + { + // TODO ooh, obituaries + this.fail("Oops! Watch out for creatures!"); + } }); } @@ -734,6 +719,29 @@ class Game { return; } + if (ev.key === ' ') { + if (this.state === 'waiting') { + // Start without moving + this.set_state('playing'); + } + else if (this.state === 'stopped') { + console.log(this.level.state); + if (this.level.state === 'success') { + // Advance to the next level + // TODO game ending? + this.load_level(this.level_index + 1); + } + else { + // Restart + this.level.restart(); + this.set_state('waiting'); + this.update_ui(); + this.redraw(); + } + return; + } + } + if (this.key_mapping[ev.key]) { this.current_keys.add(ev.key); ev.stopPropagation(); @@ -813,6 +821,7 @@ class Game { } load_level(level_index) { + // TODO clear out input? (when restarting, too?) this.level_index = level_index; this.level = new Level(this.stored_game.levels[level_index]); // waiting: haven't yet pressed a key so the timer isn't going @@ -1085,8 +1094,15 @@ async function main() { // TODO error handling :( let stored_game; if (query.get('setpath')) { - stored_game = new format_util.StoredGame; - stored_game.levels.push(c2m.parse_level(await fetch(query.get('setpath')))); + let path = query.get('setpath'); + let data = await fetch(path); + if (path.match(/\.(?:dat|ccl)$/i)) { + stored_game = dat.parse_game(data); + } + else { + stored_game = new format_util.StoredGame; + stored_game.levels.push(c2m.parse_level(data)); + } } else { // TODO also support tile world's DAC when reading from local?? diff --git a/js/tileset.js b/js/tileset.js index e687daa..f1fa164 100644 --- a/js/tileset.js +++ b/js/tileset.js @@ -17,6 +17,8 @@ export const CC2_TILESET_LAYOUT = { cloner: [15, 1], floor: [0, 2], + wall_invisible: [0, 2], + wall_appearing: [0, 2], wall: [1, 2], floor_letter: [2, 2], 'floor_letter#ascii': { @@ -108,7 +110,14 @@ export const CC2_TILESET_LAYOUT = { green_floor: [[0, 9], [1, 9], [2, 9], [3, 9]], purple_floor: [[4, 9], [5, 9], [6, 9], [7, 9]], - // TODO [8, 9] is used as an overlay for green wall + green_wall: { + base: 'green_floor', + overlay: [8, 9], + }, + purple_wall: { + base: 'purple_floor', + overlay: [8, 9], + }, // TODO state (10 is closed) trap: [9, 9], button_gray: [11, 9], @@ -384,6 +393,14 @@ export class Tileset { let drawspec = this.layout[name]; let coords = drawspec; if (! coords) console.error(name); + + let overlay; + if (coords.overlay) { + // Goofy overlay thing used for green/purple toggle tiles + overlay = coords.overlay; + coords = this.layout[coords.base]; + } + if (!(coords instanceof Array)) { // Must be an object of directions coords = coords[tile.direction ?? 'south']; @@ -393,6 +410,9 @@ export class Tileset { } this.blit(ctx, coords[0], coords[1], x, y); + if (overlay) { + this.blit(ctx, overlay[0], overlay[1], x, y); + } // Special behavior for special objects // TODO? hardcode this less? diff --git a/js/tiletypes.js b/js/tiletypes.js index 984a8f8..198e4cf 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -1,3 +1,5 @@ +import { DIRECTIONS } from './defs.js'; + const TILE_TYPES = { // Floors and walls floor: { @@ -11,6 +13,7 @@ const TILE_TYPES = { blocks: true, }, wall_invisible: { + // TODO cc2 seems to make these flicker briefly blocks: true, }, wall_appearing: { @@ -36,6 +39,9 @@ const TILE_TYPES = { thinwall_w: { thin_walls: new Set(['west']), }, + thinwall_se: { + thin_walls: new Set(['south', 'east']), + }, fake_wall: { blocks: true, on_bump(me, level, other) { @@ -219,6 +225,11 @@ const TILE_TYPES = { } }, bomb: { + // TODO explode + on_arrive(me, level, other) { + me.destroy(); + other.destroy(); + } }, thief_tools: { on_arrive(me, level, other) { @@ -246,20 +257,73 @@ const TILE_TYPES = { }, // Mechanisms - cloner: { - blocks: true, - }, dirt_block: { blocks: true, is_object: true, is_block: true, }, + green_floor: {}, + green_wall: { + blocks: true, + }, + cloner: { + // TODO ??? + blocks: true, + }, + trap: { + // TODO ??? + }, + teleport_blue: { + // TODO + }, + // Buttons + button_blue: { + on_arrive(me, level, other) { + // Flip direction of all tanks + for (let actor of level.actors) { + // TODO generify somehow?? + if (actor.type.name === 'tank_blue') { + actor.direction = DIRECTIONS[actor.direction].opposite; + } + } + } + }, + button_green: { + on_arrive(me, level, other) { + // Swap green floors and walls + for (let row of level.cells) { + for (let cell of row) { + for (let tile of cell) { + if (tile.type.name === 'green_floor') { + tile.become('green_wall'); + } + else if (tile.type.name === 'green_wall') { + tile.become('green_floor'); + } + else if (tile.type.name === 'green_chip') { + tile.become('green_bomb'); + } + else if (tile.type.name === 'green_bomb') { + tile.become('green_chip'); + } + } + } + } + } + }, + button_brown: { + // TODO how do i implement this. + }, + button_red: { + // TODO + }, // Critters bug: { is_actor: true, is_object: true, is_monster: true, + blocks_monsters: true, movement_mode: 'follow-left', movement_speed: 4, }, @@ -267,6 +331,7 @@ const TILE_TYPES = { is_actor: true, is_object: true, is_monster: true, + blocks_monsters: true, movement_mode: 'follow-right', movement_speed: 4, }, @@ -274,25 +339,45 @@ const TILE_TYPES = { is_actor: true, is_object: true, is_monster: true, + blocks_monsters: true, movement_mode: 'bounce', movement_speed: 4, }, + walker: { + is_actor: true, + is_object: true, + is_monster: true, + blocks_monsters: true, + // TODO movement_move: 'bounce-random', + movement_speed: 4, + }, + tank_blue: { + is_actor: true, + is_object: true, + is_monster: true, + blocks_monsters: true, + movement_mode: 'forward', + movement_speed: 4, + }, blob: { is_actor: true, is_object: true, is_monster: true, + blocks_monsters: true, movement_speed: 8, }, teeth: { is_actor: true, is_object: true, is_monster: true, + blocks_monsters: true, movement_speed: 4, }, fireball: { is_actor: true, is_object: true, is_monster: true, + blocks_monsters: true, movement_mode: 'turn-right', movement_speed: 4, ignores: new Set(['fire']), @@ -301,6 +386,7 @@ const TILE_TYPES = { is_actor: true, is_object: true, is_monster: true, + blocks_monsters: true, movement_mode: 'turn-left', movement_speed: 4, ignores: new Set(['water']), diff --git a/style.css b/style.css index 25b8d83..9e1f3b4 100644 --- a/style.css +++ b/style.css @@ -125,6 +125,7 @@ main > header > nav { dl.score-chart { display: grid; grid-auto-columns: 1fr 1fr; + font-size: 1.25rem; font-weight: normal; } dl.score-chart dt {