Rewrite movement loop and timing to better match Lynx rules

This commit is contained in:
Eevee (Evelyn Woods) 2020-08-30 01:50:34 -06:00
parent 413c511fe1
commit 731d3c15a8
2 changed files with 94 additions and 73 deletions

View File

@ -102,6 +102,7 @@ class Tile {
this.direction = direction; this.direction = direction;
this.slide_mode = null; this.slide_mode = null;
this.movement_cooldown = 0;
if (type.has_inventory) { if (type.has_inventory) {
this.inventory = {}; this.inventory = {};
@ -279,97 +280,107 @@ class Level {
} }
} }
halftic() { advance_tic(player_direction) {
if (this.state !== 'playing') { if (this.state !== 'playing') {
console.warn(`Level.halftic() called when state is ${this.state}`); console.warn(`Level.advance_tic() called when state is ${this.state}`);
return;
}
for (let actor of this.actors) {
if (actor.slide_mode !== null) {
// TODO do we stop sliding if we hit something, too?
this.attempt_step(actor, actor.direction);
}
if (this.state === 'success' || this.state === 'failure')
break;
}
}
advance(player_direction) {
if (this.state !== 'playing') {
console.warn(`Level.advance() called when state is ${this.state}`);
return; return;
} }
// XXX this entire turn order is rather different in ms rules
for (let actor of this.actors) { for (let actor of this.actors) {
// TODO strip these out maybe?? // TODO strip these out maybe??
if (actor.doomed) if (actor.doomed)
continue; continue;
// Actors can't make voluntary moves on ice if (actor.movement_cooldown > 0) {
if (actor.slide_mode === 'ice') actor.movement_cooldown -= 1;
if (actor.movement_cooldown > 0)
continue; continue;
}
let direction_preference;
// Actors can't make voluntary moves on ice, so they're stuck with
// whatever they've got
if (actor.slide_mode === 'ice') {
direction_preference = [actor.direction];
}
else if (actor.slide_mode === 'force') {
// Only the player can make voluntary moves on a force floor,
// and only if their previous move was an /involuntary/ move on
// a force floor. If they do, it overrides the forced move
// XXX this in particular has some subtleties in lynx (e.g. you
// can override forwards??) and DEFINITELY all kinds of stuff
// in ms
if (actor === this.player &&
player_direction &&
actor.last_move_was_force)
{
direction_preference = [player_direction];
actor.last_move_was_force = false;
}
else {
direction_preference = [actor.direction];
if (actor === this.player) { if (actor === this.player) {
actor.last_move_was_force = true;
}
}
}
else if (actor === this.player) {
if (player_direction) { if (player_direction) {
actor.direction = player_direction; direction_preference = [player_direction];
this.attempt_step(actor, player_direction); actor.last_move_was_force = false;
} }
} }
else if (actor.type.movement_mode === 'follow-left') { else if (actor.type.movement_mode === 'follow-left') {
// bug behavior: always try turning as left as possible, and // bug behavior: always try turning as left as possible, and
// fall back to less-left turns when that fails // fall back to less-left turns when that fails
let direction = DIRECTIONS[actor.direction].left; let d = DIRECTIONS[actor.direction];
for (let i = 0; i < 4; i++) { direction_preference = [d.left, actor.direction, d.right, d.opposite];
if (this.attempt_step(actor, direction)) {
actor.direction = direction;
break;
}
direction = DIRECTIONS[direction].right;
}
} }
else if (actor.type.movement_mode === 'follow-right') { else if (actor.type.movement_mode === 'follow-right') {
// paramecium behavior: always try turning as right as // paramecium behavior: always try turning as right as
// possible, and fall back to less-right turns when that fails // possible, and fall back to less-right turns when that fails
let direction = DIRECTIONS[actor.direction].right; let d = DIRECTIONS[actor.direction];
for (let i = 0; i < 4; i++) { direction_preference = [d.right, actor.direction, d.left, d.opposite];
if (this.attempt_step(actor, direction)) {
actor.direction = direction;
break;
}
direction = DIRECTIONS[direction].left;
}
} }
else if (actor.type.movement_mode === 'turn-left') { else if (actor.type.movement_mode === 'turn-left') {
// glider behavior: preserve current direction; if that doesn't // glider behavior: preserve current direction; if that doesn't
// work, turn left, then right, then back the way we came // work, turn left, then right, then back the way we came
for (let direction of [ let d = DIRECTIONS[actor.direction];
actor.direction, direction_preference = [actor.direction, d.left, d.right, d.opposite];
DIRECTIONS[actor.direction].left,
DIRECTIONS[actor.direction].right,
DIRECTIONS[actor.direction].opposite,
]) {
if (this.attempt_step(actor, direction)) {
actor.direction = direction;
break;
}
}
} }
else if (actor.type.movement_mode === 'turn-right') { else if (actor.type.movement_mode === 'turn-right') {
// fireball behavior: preserve current direction; if that doesn't // fireball behavior: preserve current direction; if that doesn't
// work, turn right, then left, then back the way we came // work, turn right, then left, then back the way we came
for (let direction of [ let d = DIRECTIONS[actor.direction];
actor.direction, direction_preference = [actor.direction, d.right, d.left, d.opposite];
DIRECTIONS[actor.direction].right, }
DIRECTIONS[actor.direction].left, else if (actor.type.movement_mode === 'bounce') {
DIRECTIONS[actor.direction].opposite, // bouncy ball behavior: preserve current direction; if that
]) { // doesn't work, bounce back the way we came
if (this.attempt_step(actor, direction)) { let d = DIRECTIONS[actor.direction];
direction_preference = [actor.direction, d.opposite];
}
if (! direction_preference)
continue;
let moved = false;
for (let direction of direction_preference) {
actor.direction = direction; actor.direction = direction;
if (this.attempt_step(actor, direction)) {
moved = true;
break; break;
} }
} }
// Always set the cooldown if we even attempt to move. Speed
// multiplier is based on the tile we landed /on/, if any.
let speed_multiplier = 1;
if (actor.slide_mode !== null) {
speed_multiplier = 2;
} }
actor.movement_cooldown = actor.type.movement_speed / speed_multiplier;
// TODO do i need to do this more aggressively? // TODO do i need to do this more aggressively?
if (this.state === 'success' || this.state === 'failure') if (this.state === 'success' || this.state === 'failure')
@ -382,6 +393,8 @@ class Level {
this.fail_message = message; this.fail_message = message;
} }
// Try to move the given actor one tile in the given direction and update
// their cooldown. Return true if successful.
attempt_step(actor, direction) { attempt_step(actor, direction) {
let move = DIRECTIONS[direction].movement; let move = DIRECTIONS[direction].movement;
let goal_x = actor.x + move[0]; let goal_x = actor.x + move[0];
@ -442,6 +455,9 @@ class Level {
return true; return true;
} }
// Move the given actor to the given position and perform any appropriate
// tile interactions. Does NOT check for whether the move is actually
// legal; use attempt_step for that!
move_to(actor, x, y) { move_to(actor, x, y) {
if (x === actor.x && y === actor.y) if (x === actor.x && y === actor.y)
return; return;
@ -644,15 +660,10 @@ class Game {
do_frame() { do_frame() {
if (this.level.state === 'playing') { if (this.level.state === 'playing') {
this.frame++; this.frame++;
if (this.frame % 6 === 0) { if (this.frame % 3 === 0) {
this.level.halftic(); this.level.advance_tic(this.next_player_move);
}
if (this.frame % 12 === 0) {
this.level.advance(this.next_player_move);
this.next_player_move = this.pending_player_move; this.next_player_move = this.pending_player_move;
this.player_used_move = true; this.player_used_move = true;
}
if (this.frame % 6 === 0) {
this.redraw(); this.redraw();
} }
this.frame %= 60; this.frame %= 60;

View File

@ -197,25 +197,25 @@ const TILE_TYPES = {
force_floor_n: { force_floor_n: {
on_arrive(me, level, other) { on_arrive(me, level, other) {
other.direction = 'north'; other.direction = 'north';
level.make_slide(other, 'push'); level.make_slide(other, 'force');
} }
}, },
force_floor_e: { force_floor_e: {
on_arrive(me, level, other) { on_arrive(me, level, other) {
other.direction = 'east'; other.direction = 'east';
level.make_slide(other, 'push'); level.make_slide(other, 'force');
} }
}, },
force_floor_s: { force_floor_s: {
on_arrive(me, level, other) { on_arrive(me, level, other) {
other.direction = 'south'; other.direction = 'south';
level.make_slide(other, 'push'); level.make_slide(other, 'force');
} }
}, },
force_floor_w: { force_floor_w: {
on_arrive(me, level, other) { on_arrive(me, level, other) {
other.direction = 'west'; other.direction = 'west';
level.make_slide(other, 'push'); level.make_slide(other, 'force');
} }
}, },
bomb: { bomb: {
@ -261,33 +261,40 @@ const TILE_TYPES = {
is_object: true, is_object: true,
is_monster: true, is_monster: true,
movement_mode: 'follow-left', movement_mode: 'follow-left',
movement_speed: 4,
}, },
paramecium: { paramecium: {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
movement_mode: 'follow-right', movement_mode: 'follow-right',
movement_speed: 4,
}, },
ball: { ball: {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
movement_mode: 'bounce',
movement_speed: 4,
}, },
blob: { blob: {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
movement_speed: 8,
}, },
teeth: { teeth: {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
movement_speed: 4,
}, },
fireball: { fireball: {
is_actor: true, is_actor: true,
is_object: true, is_object: true,
is_monster: true, is_monster: true,
movement_mode: 'turn-right', movement_mode: 'turn-right',
movement_speed: 4,
ignores: new Set(['fire']), ignores: new Set(['fire']),
}, },
glider: { glider: {
@ -295,6 +302,7 @@ const TILE_TYPES = {
is_object: true, is_object: true,
is_monster: true, is_monster: true,
movement_mode: 'turn-left', movement_mode: 'turn-left',
movement_speed: 4,
ignores: new Set(['water']), ignores: new Set(['water']),
}, },
@ -356,9 +364,11 @@ const TILE_TYPES = {
is_player: true, is_player: true,
has_inventory: true, has_inventory: true,
is_object: true, is_object: true,
movement_speed: 4,
pushes: { pushes: {
dirt_block: true, dirt_block: true,
}, },
// FIXME this prevents thief from taking green key
infinite_items: { infinite_items: {
key_green: true, key_green: true,
}, },