-
+
Chips
diff --git a/js/game.js b/js/game.js
index 853fd08..7974ab3 100644
--- a/js/game.js
+++ b/js/game.js
@@ -622,7 +622,7 @@ export class Level {
this.time_remaining -= 1;
if (this.time_remaining <= 0) {
- this.fail("Time's up!");
+ this.fail('time');
}
}
else {
@@ -767,31 +767,28 @@ export class Level {
}
// Check for a couple effects that always apply immediately
- // TODO i guess this covers blocks too
// TODO do blocks smash monsters?
for (let tile of goal_cell) {
+ if (actor.type.is_player && tile.type.is_monster) {
+ this.fail(tile.type.name);
+ }
+ else if (actor.type.is_monster && tile.type.is_player) {
+ this.fail(actor.type.name);
+ }
+ else if (actor.type.is_block && tile.type.is_player) {
+ this.fail('squished');
+ }
+
if (tile.type.slide_mode && ! actor.ignores(tile.type.name)) {
this.make_slide(actor, tile.type.slide_mode);
}
- 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!");
- return;
- }
- if (actor.type.is_block && tile.type.is_player) {
- // TODO ooh, obituaries
- this.fail("squish");
- return;
- }
}
// If we're stepping directly on the player, that kills them too
// TODO this only works because i have the player move first; in lynx the check is the other
// way around
if (actor.type.is_monster && goal_cell === this.player_leaving_cell) {
- this.fail("Oops! Watch out for creatures!");
+ this.fail(actor.type.name);
}
if (this.compat.tiles_react_instantly) {
@@ -931,7 +928,7 @@ export class Level {
this.time_remaining = 1;
}
else {
- this.fail("Time's up!");
+ this.fail('time');
}
}
}
@@ -939,10 +936,10 @@ export class Level {
fail(message) {
this.pending_undo.push(() => {
this.state = 'playing';
- this.fail_message = null;
+ this.fail_reason = null;
});
this.state = 'failure';
- this.fail_message = message;
+ this.fail_reason = message;
throw new GameEnded;
}
diff --git a/js/main.js b/js/main.js
index 58c82d9..309d735 100644
--- a/js/main.js
+++ b/js/main.js
@@ -8,7 +8,7 @@ import { Level } from './game.js';
import CanvasRenderer from './renderer-canvas.js';
import { Tileset, CC2_TILESET_LAYOUT, TILE_WORLD_TILESET_LAYOUT } from './tileset.js';
import TILE_TYPES from './tiletypes.js';
-import { mk, promise_event, fetch, walk_grid } from './util.js';
+import { random_choice, mk, promise_event, fetch, walk_grid } from './util.js';
const PAGE_TITLE = "Lexy's Labyrinth";
// Stackable modal overlay of some kind, usually a dialog
@@ -136,6 +136,65 @@ const ACTION_DIRECTIONS = {
left: 'west',
right: 'east',
};
+const OBITUARIES = {
+ drowned: [
+ "player tried out water cooling",
+ "player fell into the c",
+ ],
+ burned: [
+ "player's core temp got too high",
+ "player's plans went up in smoke",
+ ],
+ exploded: [
+ "player didn't watch where they step",
+ "player is having a blast",
+ "player tripped over something of mine",
+ ],
+ squished: [
+ "player was overwhelmed by a block of ram",
+ "player became two-dimensional",
+ ],
+ time: [
+ "player tried to overclock",
+ "player's time ran out",
+ "player's speedrun went badly",
+ ],
+ generic: [
+ "player had a bad time",
+ ],
+
+ // Specific creatures
+ ball: [
+ "player is having a ball",
+ ],
+ walker: [
+ "player let it walk all over them",
+ ],
+ fireball: [
+ "player had a meltdown",
+ ],
+ glider: [
+ "player's ship came in",
+ ],
+ tank_blue: [
+ "player didn't watch where they tread",
+ ],
+ tank_yellow: [
+ "player let things get out of control",
+ ],
+ bug: [
+ "player has ants in their pants",
+ ],
+ paramecium: [
+ "player has the creepy crawlies",
+ ],
+ teeth: [
+ "player got a mega bite",
+ ],
+ blob: [
+ "player didn't do a gooed job",
+ ],
+};
class Player extends PrimaryView {
constructor(conductor) {
super(conductor, document.body.querySelector('main#player'));
@@ -163,12 +222,12 @@ class Player extends PrimaryView {
this.root.style.setProperty('--tile-width', `${this.conductor.tileset.size_x}px`);
this.root.style.setProperty('--tile-height', `${this.conductor.tileset.size_y}px`);
this.level_el = this.root.querySelector('.level');
+ this.overlay_message_el = this.root.querySelector('.overlay-message');
this.message_el = this.root.querySelector('.message');
this.chips_el = this.root.querySelector('.chips output');
this.time_el = this.root.querySelector('.time output');
this.bonus_el = this.root.querySelector('.bonus output');
this.inventory_el = this.root.querySelector('.inventory');
- this.bummer_el = this.root.querySelector('.bummer');
this.input_el = this.root.querySelector('.input');
this.demo_el = this.root.querySelector('.demo');
@@ -589,42 +648,90 @@ class Player extends PrimaryView {
this.state = new_state;
+ // Populate the overlay
+ let overlay_reason = '';
+ let overlay_top = '';
+ let overlay_middle = null;
+ let overlay_bottom = '';
if (this.state === 'waiting') {
- this.bummer_el.textContent = "Ready!";
- }
- else if (this.state === 'playing' || this.state === 'rewinding') {
- this.bummer_el.textContent = "";
+ overlay_reason = 'waiting';
+ overlay_middle = "Ready!";
}
else if (this.state === 'paused') {
- this.bummer_el.textContent = "/// paused ///";
+ overlay_reason = 'paused';
+ overlay_bottom = "/// paused ///";
}
else if (this.state === 'stopped') {
if (this.level.state === 'failure') {
- this.bummer_el.textContent = this.level.fail_message;
+ overlay_reason = 'failure';
+ overlay_top = "whoops";
+ let obits = OBITUARIES[this.level.fail_reason] ?? OBITUARIES['generic'];
+ overlay_bottom = random_choice(obits);
}
else {
- this.bummer_el.textContent = "";
+ overlay_reason = 'success';
let base = (this.conductor.level_index + 1) * 500;
let time = Math.ceil((this.level.time_remaining ?? 0) / 20) * 10;
- this.bummer_el.append(
- mk('p', "go bit buster!"),
- mk('dl.score-chart',
- mk('dt', "base score"),
- mk('dd', base),
- mk('dt', "time bonus"),
- mk('dd', `+ ${time}`),
- mk('dt', "score bonus"),
- mk('dd', `+ ${this.level.bonus_points}`),
- mk('dt.-sum', "level score"),
- mk('dd.-sum', base + time + this.level.bonus_points),
- mk('dt', "improvement"),
- mk('dd', "(TODO)"),
- mk('dt', "total score"),
- mk('dd', "(TODO)"),
- ),
+ // Pick a success message
+ // TODO done on first try; took many tries
+ let time_left_fraction = null;
+ if (this.level.time_remaining !== null && this.level.stored_level.time_limit !== null) {
+ time_left_fraction = this.level.time_remaining / 20 / this.level.stored_level.time_limit;
+ }
+
+ if (this.level.chips_remaining > 0) {
+ overlay_top = random_choice([
+ "socket to em!", "go bug blaster!",
+ ]);
+ }
+ else if (this.level.time_remaining && this.level.time_remaining < 200) {
+ overlay_top = random_choice([
+ "in the nick of time!", "cutting it close!",
+ ]);
+ }
+ else if (time_left_fraction !== null && time_left_fraction > 1) {
+ overlay_top = random_choice([
+ "faster than light!", "impossible speed!", "pipelined!",
+ ]);
+ }
+ else if (time_left_fraction !== null && time_left_fraction > 0.75) {
+ overlay_top = random_choice([
+ "lightning quick!", "nice speedrun!", "eagerly evaluated!",
+ ]);
+ }
+ else {
+ overlay_top = random_choice([
+ "you did it!", "nice going!", "great job!", "good work!",
+ "onwards!", "tubular!", "yeehaw!", "hot damn!",
+ "alphanumeric!", "nice dynamic typing!",
+ ]);
+ }
+ overlay_bottom = "press spacebar to continue";
+
+ overlay_middle = mk('dl.score-chart',
+ mk('dt', "base score"),
+ mk('dd', base),
+ mk('dt', "time bonus"),
+ mk('dd', `+ ${time}`),
+ mk('dt', "score bonus"),
+ mk('dd', `+ ${this.level.bonus_points}`),
+ mk('dt.-sum', "level score"),
+ mk('dd.-sum', base + time + this.level.bonus_points),
+ mk('dt', "improvement"),
+ mk('dd', "(TODO)"),
+ mk('dt', "total score"),
+ mk('dd', "(TODO)"),
);
}
}
+ this.overlay_message_el.setAttribute('data-reason', overlay_reason);
+ this.overlay_message_el.querySelector('.-top').textContent = overlay_top;
+ this.overlay_message_el.querySelector('.-bottom').textContent = overlay_bottom;
+ let middle = this.overlay_message_el.querySelector('.-middle');
+ middle.textContent = '';
+ if (overlay_middle) {
+ middle.append(overlay_middle);
+ }
// The advance and redraw methods run in a loop, but they cancel
// themselves if the game isn't running, so restart them here
diff --git a/js/tiletypes.js b/js/tiletypes.js
index 4fc04cf..bc73fbb 100644
--- a/js/tiletypes.js
+++ b/js/tiletypes.js
@@ -291,7 +291,7 @@ const TILE_TYPES = {
}
else if (other.type.is_player) {
level.transmute_tile(other, 'player_burned');
- level.fail("Oops! You can't walk on fire without fire boots!");
+ level.fail('burned');
}
else {
level.remove_tile(other);
@@ -312,7 +312,7 @@ const TILE_TYPES = {
}
else if (other.type.is_player) {
level.transmute_tile(other, 'splash');
- level.fail("swimming with the fishes");
+ level.fail('drowned');
}
else {
level.transmute_tile(other, 'splash');
@@ -436,7 +436,7 @@ const TILE_TYPES = {
let was_player = other.type.is_player;
level.transmute_tile(other, 'explosion');
if (was_player) {
- level.fail("watch where you step");
+ level.fail('exploded');
}
},
},
@@ -555,7 +555,7 @@ const TILE_TYPES = {
let was_player = other.type.is_player;
level.transmute_tile(other, 'explosion');
if (was_player) {
- level.fail("watch where you step");
+ level.fail('exploded');
}
},
},
diff --git a/js/util.js b/js/util.js
index e41027e..9ca5486 100644
--- a/js/util.js
+++ b/js/util.js
@@ -1,3 +1,7 @@
+export function random_choice(list) {
+ return list[Math.floor(Math.random() * list.length)];
+}
+
export function mk(tag_selector, ...children) {
let [tag, ...classes] = tag_selector.split('.');
let el = document.createElement(tag);
diff --git a/style.css b/style.css
index 41ba6f6..5bea114 100644
--- a/style.css
+++ b/style.css
@@ -323,34 +323,46 @@ body[data-mode=player] #editor-play {
--viewport-width: 9;
--viewport-height: 9;
}
-.bummer {
+#player .overlay-message {
grid-area: level;
place-self: stretch;
- display: flex;
- flex-direction: column;
+ display: grid;
+ grid-template-rows: 1fr 3fr 1fr;
justify-content: center;
align-items: center;
z-index: 1;
- font-size: 48px;
- padding: 10%;
+ font-size: calc(0.5 * var(--tile-width) * var(--scale));
+ padding: 6.25%;
background: #0009;
color: white;
text-align: center;
- font-weight: bold;
text-shadow: 0 2px 1px black;
}
-.bummer:empty {
+#player .overlay-message p {
+ margin: 0;
+}
+#player .overlay-message .-top {
+ font-size: 1.5em;
+}
+#player .overlay-message .-middle {
+}
+#player .overlay-message .-bottom {
+}
+#player .overlay-message[data-reason=""] {
display: none;
}
-.bummer p {
- margin: 0;
+#player .overlay-message[data-reason=failure] {
+ box-shadow: inset 0 0 calc(4 * var(--tile-width)) var(--tile-width) black;
+}
+#player .overlay-message[data-reason=success] {
+ box-shadow: inset 0 0 var(--tile-width) white;
}
dl.score-chart {
display: grid;
grid-auto-columns: 1fr 1fr;
- font-size: 1.25rem;
+ margin: auto;
font-weight: normal;
}
dl.score-chart dt {