Implement green/blue buttons, tanks, monster death; stub out remaining CC1 objects; easy restart on death

This commit is contained in:
Eevee (Evelyn Woods) 2020-08-31 11:03:14 -06:00
parent 0390d54909
commit 070d276e8a
5 changed files with 184 additions and 35 deletions

26
js/defs.js Normal file
View File

@ -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',
},
};

View File

@ -5,6 +5,7 @@ import * as dat from './format-dat.js';
import * as format_util from './format-util.js'; import * as format_util from './format-util.js';
import TILE_TYPES from './tiletypes.js'; import TILE_TYPES from './tiletypes.js';
import { Tileset, CC2_TILESET_LAYOUT, TILE_WORLD_TILESET_LAYOUT } from './tileset.js'; import { Tileset, CC2_TILESET_LAYOUT, TILE_WORLD_TILESET_LAYOUT } from './tileset.js';
import { DIRECTIONS } from './defs.js';
function mk(tag_selector, ...children) { function mk(tag_selector, ...children) {
let [tag, ...classes] = tag_selector.split('.'); let [tag, ...classes] = tag_selector.split('.');
@ -67,33 +68,6 @@ async function fetch(url) {
const PAGE_TITLE = "Lexy's Labyrinth"; 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 { class Tile {
constructor(type, x, y, direction = 'south') { constructor(type, x, y, direction = 'south') {
this.type = type; this.type = type;
@ -231,16 +205,16 @@ class Level {
this.width = stored_level.size_x; this.width = stored_level.size_x;
this.height = stored_level.size_y; this.height = stored_level.size_y;
this.restart(); this.restart();
}
restart() {
// playing: normal play // playing: normal play
// success: has been won // success: has been won
// failure: died // failure: died
// note that pausing is NOT handled here, but by whatever's driving our // note that pausing is NOT handled here, but by whatever's driving our
// event loop! // event loop!
this.state = 'playing'; this.state = 'playing';
}
restart() {
this.cells = []; this.cells = [];
this.player = null; this.player = null;
this.actors = []; this.actors = [];
@ -371,6 +345,10 @@ class Level {
let d = DIRECTIONS[actor.direction]; let d = DIRECTIONS[actor.direction];
direction_preference = [actor.direction, d.opposite]; 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) if (! direction_preference)
continue; continue;
@ -531,6 +509,13 @@ class Level {
else if (tile.type.on_arrive) { else if (tile.type.on_arrive) {
tile.type.on_arrive(tile, this, actor); 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; 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]) { if (this.key_mapping[ev.key]) {
this.current_keys.add(ev.key); this.current_keys.add(ev.key);
ev.stopPropagation(); ev.stopPropagation();
@ -813,6 +821,7 @@ class Game {
} }
load_level(level_index) { load_level(level_index) {
// TODO clear out input? (when restarting, too?)
this.level_index = level_index; this.level_index = level_index;
this.level = new Level(this.stored_game.levels[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 // waiting: haven't yet pressed a key so the timer isn't going
@ -1085,8 +1094,15 @@ async function main() {
// TODO error handling :( // TODO error handling :(
let stored_game; let stored_game;
if (query.get('setpath')) { if (query.get('setpath')) {
stored_game = new format_util.StoredGame; let path = query.get('setpath');
stored_game.levels.push(c2m.parse_level(await fetch(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 { else {
// TODO also support tile world's DAC when reading from local?? // TODO also support tile world's DAC when reading from local??

View File

@ -17,6 +17,8 @@ export const CC2_TILESET_LAYOUT = {
cloner: [15, 1], cloner: [15, 1],
floor: [0, 2], floor: [0, 2],
wall_invisible: [0, 2],
wall_appearing: [0, 2],
wall: [1, 2], wall: [1, 2],
floor_letter: [2, 2], floor_letter: [2, 2],
'floor_letter#ascii': { 'floor_letter#ascii': {
@ -108,7 +110,14 @@ export const CC2_TILESET_LAYOUT = {
green_floor: [[0, 9], [1, 9], [2, 9], [3, 9]], green_floor: [[0, 9], [1, 9], [2, 9], [3, 9]],
purple_floor: [[4, 9], [5, 9], [6, 9], [7, 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) // TODO state (10 is closed)
trap: [9, 9], trap: [9, 9],
button_gray: [11, 9], button_gray: [11, 9],
@ -384,6 +393,14 @@ export class Tileset {
let drawspec = this.layout[name]; let drawspec = this.layout[name];
let coords = drawspec; let coords = drawspec;
if (! coords) console.error(name); 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)) { if (!(coords instanceof Array)) {
// Must be an object of directions // Must be an object of directions
coords = coords[tile.direction ?? 'south']; coords = coords[tile.direction ?? 'south'];
@ -393,6 +410,9 @@ export class Tileset {
} }
this.blit(ctx, coords[0], coords[1], x, y); 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 // Special behavior for special objects
// TODO? hardcode this less? // TODO? hardcode this less?

View File

@ -1,3 +1,5 @@
import { DIRECTIONS } from './defs.js';
const TILE_TYPES = { const TILE_TYPES = {
// Floors and walls // Floors and walls
floor: { floor: {
@ -11,6 +13,7 @@ const TILE_TYPES = {
blocks: true, blocks: true,
}, },
wall_invisible: { wall_invisible: {
// TODO cc2 seems to make these flicker briefly
blocks: true, blocks: true,
}, },
wall_appearing: { wall_appearing: {
@ -36,6 +39,9 @@ const TILE_TYPES = {
thinwall_w: { thinwall_w: {
thin_walls: new Set(['west']), thin_walls: new Set(['west']),
}, },
thinwall_se: {
thin_walls: new Set(['south', 'east']),
},
fake_wall: { fake_wall: {
blocks: true, blocks: true,
on_bump(me, level, other) { on_bump(me, level, other) {
@ -219,6 +225,11 @@ const TILE_TYPES = {
} }
}, },
bomb: { bomb: {
// TODO explode
on_arrive(me, level, other) {
me.destroy();
other.destroy();
}
}, },
thief_tools: { thief_tools: {
on_arrive(me, level, other) { on_arrive(me, level, other) {
@ -246,20 +257,73 @@ const TILE_TYPES = {
}, },
// Mechanisms // Mechanisms
cloner: {
blocks: true,
},
dirt_block: { dirt_block: {
blocks: true, blocks: true,
is_object: true, is_object: true,
is_block: 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 // Critters
bug: { bug: {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
blocks_monsters: true,
movement_mode: 'follow-left', movement_mode: 'follow-left',
movement_speed: 4, movement_speed: 4,
}, },
@ -267,6 +331,7 @@ const TILE_TYPES = {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
blocks_monsters: true,
movement_mode: 'follow-right', movement_mode: 'follow-right',
movement_speed: 4, movement_speed: 4,
}, },
@ -274,25 +339,45 @@ const TILE_TYPES = {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
blocks_monsters: true,
movement_mode: 'bounce', movement_mode: 'bounce',
movement_speed: 4, 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: { blob: {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
blocks_monsters: true,
movement_speed: 8, movement_speed: 8,
}, },
teeth: { teeth: {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
blocks_monsters: true,
movement_speed: 4, movement_speed: 4,
}, },
fireball: { fireball: {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
blocks_monsters: true,
movement_mode: 'turn-right', movement_mode: 'turn-right',
movement_speed: 4, movement_speed: 4,
ignores: new Set(['fire']), ignores: new Set(['fire']),
@ -301,6 +386,7 @@ const TILE_TYPES = {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
blocks_monsters: true,
movement_mode: 'turn-left', movement_mode: 'turn-left',
movement_speed: 4, movement_speed: 4,
ignores: new Set(['water']), ignores: new Set(['water']),

View File

@ -125,6 +125,7 @@ main > header > nav {
dl.score-chart { dl.score-chart {
display: grid; display: grid;
grid-auto-columns: 1fr 1fr; grid-auto-columns: 1fr 1fr;
font-size: 1.25rem;
font-weight: normal; font-weight: normal;
} }
dl.score-chart dt { dl.score-chart dt {