Implement green/blue buttons, tanks, monster death; stub out remaining CC1 objects; easy restart on death
This commit is contained in:
parent
0390d54909
commit
070d276e8a
26
js/defs.js
Normal file
26
js/defs.js
Normal 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',
|
||||
},
|
||||
};
|
||||
76
js/main.js
76
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')) {
|
||||
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(await fetch(query.get('setpath'))));
|
||||
stored_game.levels.push(c2m.parse_level(data));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO also support tile world's DAC when reading from local??
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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']),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user