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 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??

View File

@ -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?

View File

@ -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']),

View File

@ -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 {