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.slide_mode = null;
this.movement_cooldown = 0;
if (type.has_inventory) {
this.inventory = {};
@ -279,98 +280,108 @@ class Level {
}
}
halftic() {
advance_tic(player_direction) {
if (this.state !== 'playing') {
console.warn(`Level.halftic() 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}`);
console.warn(`Level.advance_tic() called when state is ${this.state}`);
return;
}
// XXX this entire turn order is rather different in ms rules
for (let actor of this.actors) {
// TODO strip these out maybe??
if (actor.doomed)
continue;
// Actors can't make voluntary moves on ice
if (actor.slide_mode === 'ice')
continue;
if (actor === this.player) {
if (actor.movement_cooldown > 0) {
actor.movement_cooldown -= 1;
if (actor.movement_cooldown > 0)
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) {
actor.last_move_was_force = true;
}
}
}
else if (actor === this.player) {
if (player_direction) {
actor.direction = player_direction;
this.attempt_step(actor, player_direction);
direction_preference = [player_direction];
actor.last_move_was_force = false;
}
}
else if (actor.type.movement_mode === 'follow-left') {
// bug behavior: always try turning as left as possible, and
// fall back to less-left turns when that fails
let direction = DIRECTIONS[actor.direction].left;
for (let i = 0; i < 4; i++) {
if (this.attempt_step(actor, direction)) {
actor.direction = direction;
break;
}
direction = DIRECTIONS[direction].right;
}
let d = DIRECTIONS[actor.direction];
direction_preference = [d.left, actor.direction, d.right, d.opposite];
}
else if (actor.type.movement_mode === 'follow-right') {
// paramecium behavior: always try turning as right as
// possible, and fall back to less-right turns when that fails
let direction = DIRECTIONS[actor.direction].right;
for (let i = 0; i < 4; i++) {
if (this.attempt_step(actor, direction)) {
actor.direction = direction;
break;
}
direction = DIRECTIONS[direction].left;
}
let d = DIRECTIONS[actor.direction];
direction_preference = [d.right, actor.direction, d.left, d.opposite];
}
else if (actor.type.movement_mode === 'turn-left') {
// glider behavior: preserve current direction; if that doesn't
// work, turn left, then right, then back the way we came
for (let direction of [
actor.direction,
DIRECTIONS[actor.direction].left,
DIRECTIONS[actor.direction].right,
DIRECTIONS[actor.direction].opposite,
]) {
if (this.attempt_step(actor, direction)) {
actor.direction = direction;
break;
}
}
let d = DIRECTIONS[actor.direction];
direction_preference = [actor.direction, d.left, d.right, d.opposite];
}
else if (actor.type.movement_mode === 'turn-right') {
// fireball behavior: preserve current direction; if that doesn't
// work, turn right, then left, then back the way we came
for (let direction of [
actor.direction,
DIRECTIONS[actor.direction].right,
DIRECTIONS[actor.direction].left,
DIRECTIONS[actor.direction].opposite,
]) {
if (this.attempt_step(actor, direction)) {
actor.direction = direction;
break;
}
let d = DIRECTIONS[actor.direction];
direction_preference = [actor.direction, d.right, d.left, d.opposite];
}
else if (actor.type.movement_mode === 'bounce') {
// bouncy ball behavior: preserve current direction; if that
// doesn't work, bounce back the way we came
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;
if (this.attempt_step(actor, direction)) {
moved = true;
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?
if (this.state === 'success' || this.state === 'failure')
break;
@ -382,6 +393,8 @@ class Level {
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) {
let move = DIRECTIONS[direction].movement;
let goal_x = actor.x + move[0];
@ -442,6 +455,9 @@ class Level {
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) {
if (x === actor.x && y === actor.y)
return;
@ -644,15 +660,10 @@ class Game {
do_frame() {
if (this.level.state === 'playing') {
this.frame++;
if (this.frame % 6 === 0) {
this.level.halftic();
}
if (this.frame % 12 === 0) {
this.level.advance(this.next_player_move);
if (this.frame % 3 === 0) {
this.level.advance_tic(this.next_player_move);
this.next_player_move = this.pending_player_move;
this.player_used_move = true;
}
if (this.frame % 6 === 0) {
this.redraw();
}
this.frame %= 60;

View File

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