Saltar al contenido
GameGenresEnglish

Te mueves por un laberinto, casi siempre visto desde arriba, y el reto es elegir bien tus rutas por sus pasillos para recoger objetos, encontrar la salida o escapar de quienes te persiguen.

También se llama: juego de laberintos, laberinto, persecución en laberinto

Mecánicas que lo definen

  • Movimiento por pasillos en una rejilla; las decisiones importantes se toman en los cruces.
  • Objetos repartidos por todo el laberinto (puntos, tesoros) que obligan a recorrerlo entero.
  • Perseguidores con reglas de movimiento simples y distintas entre sí, que el jugador aprende a predecir.
  • Potenciadores que invierten la persecución por un tiempo y cambian quién caza a quién.

Bucle de juego

Leer dónde están los perseguidores, elegir una ruta que recoja lo más posible con poco riesgo, recorrerla y corregirla en cada cruce.

Demo propia

Controles. Flechas o WASD para girar; puedes pedir el giro antes de llegar al cruce. Con ratón o tacto, toca hacia dónde quieres ir respecto del jugador, o usa los botones en pantalla.

Abrir en pantalla completa

Un laberinto de rejilla con puntos que recoger, tres perseguidores con comportamientos distintos y cuatro potenciadores que invierten la persecución durante unos segundos. Lo justo para sentir lo que define al género, que es elegir rutas bajo presión.

Hecha con Kaplay, en unas 195 líneas de JavaScript.

Cómo está hecha

  1. Configuración y controles

    Se definen botones virtuales para las cuatro direcciones, así el mismo código sirve con teclado o con pantalla táctil. Las velocidades del jugador, de los perseguidores y del modo asustado, y la duración del potenciador son los números que fijan la dificultad.

    const k = kaplay({
      width: 640,
      height: 360,
      letterbox: true, pixelDensity,
      background: [12, 12, 30],
      // Virtual buttons: the game asks about "up", not about a specific key.
      buttons: {
        left: { keyboard: ["left", "a"] },
        right: { keyboard: ["right", "d"] },
        up: { keyboard: ["up", "w"] },
        down: { keyboard: ["down", "s"] },
        start: { keyboard: ["space", "enter"] },
      },
    });
    touchControls(k, [
      { button: "left", label: "◀" },
      { button: "right", label: "▶" },
      { button: "up", label: "▲", side: "right" },
      { button: "down", label: "▼" },
    ]);
    
    const TILE = 18;
    const PLAYER_SPEED = 7; // cells per second
    const GHOST_SPEED = 6;
    const FRIGHTENED_SPEED = 3.5;
    const POWER_TIME = 6; // seconds
    const DIRECTIONS = { left: vec2(-1, 0), right: vec2(1, 0), up: vec2(0, -1), down: vec2(0, 1) };
  2. El laberinto como texto

    El laberinto es una lista de cadenas; cada carácter es un muro, un punto, un potenciador o la casilla de salida de un personaje. isFree() responde si una casilla se puede pisar y cellToScreen() la convierte en píxeles. Toda la lógica trabaja en casillas, no en píxeles.

    // The maze is a text drawing: # wall · . dot · o power-up
    // P player · R, S, N the three chasers (red, pink and orange).
    const MAP = [
      "#####################",
      "#.........#.........#",
      "#o###.###.#.###.###o#",
      "#...................#",
      "#.###.#.#####.#.###.#",
      "#.....#...#...#.....#",
      "#####.###.#.###.#####",
      "#.........R.........#",
      "#.###.###.#.###.###.#",
      "#...#.....S.....#...#",
      "###.#.#.#####.#.#.###",
      "#.....#...N...#.....#",
      "#.#######.#.#######.#",
      "#.........P.........#",
      "#.###.###.#.###.###.#",
      "#o..#...........#..o#",
      "#.#.#.###.#.###.#.#.#",
      "#...................#",
      "#####################",
    ];
    const X0 = (640 - MAP[0].length * TILE) / 2;
    const Y0 = (360 - MAP.length * TILE) / 2;
    const isFree = (c) => MAP[c.y]?.[c.x] !== undefined && MAP[c.y][c.x] !== "#";
    const cellToScreen = (c) => vec2(X0 + c.x * TILE + TILE / 2, Y0 + c.y * TILE + TILE / 2);
    const findCell = (char) => {
      const y = MAP.findIndex((row) => row.includes(char));
      return vec2(MAP[y].indexOf(char), y);
    };
  3. Moverse por la rejilla

    Jugador y perseguidores se mueven igual, de centro de casilla a centro de casilla, con una interpolación para que se vea suave. Solo al llegar a una casilla se decide la dirección siguiente, así que las decisiones ocurren en los cruces, que es donde el género pone el interés.

    // Everyone moves the same way: from cell centre to cell centre. On reaching a
    // cell, `choose` picks the next direction. So movement reads as a grid and
    // decisions only happen at junctions.
    function makeActor(home, comps) {
      const a = add([...comps, pos(cellToScreen(home)), anchor("center")]);
      Object.assign(a, { home, cell: home, next: home, dir: vec2(0), progress: 1 });
      return a;
    }
    function step(a, speed, chooseDir) {
      a.progress += dt() * speed;
      if (a.progress >= 1) {
        a.cell = a.next;
        a.arrive?.();
        a.dir = chooseDir(a);
        const ahead = a.cell.add(a.dir);
        if (isFree(ahead)) (a.next = ahead), (a.progress = 0);
        else (a.dir = vec2(0)), (a.progress = 1);
      }
      a.pos = cellToScreen(a.cell).lerp(cellToScreen(a.next), Math.min(a.progress, 1));
    }
  4. Un control que perdona

    El jugador recuerda la última dirección pedida y la toma en cuanto el pasillo lo permite, de modo que puedes girar un poco antes de llegar al cruce. Darse la vuelta es inmediato. Sin estos dos detalles, escapar en un pasillo se siente injusto.

    // The player remembers the last direction asked for and takes it as soon as
    // the corridor allows: turning "before" reaching the junction makes the
    // controls feel precise. Turning around is instant.
    const player = makeActor(findCell("P"), [circle(7), color(255, 225, 60)]);
    let wanted = vec2(0);
    const request = (d) => {
      wanted = d;
      if (d.eq(player.dir.scale(-1))) {
        [player.cell, player.next] = [player.next, player.cell];
        player.dir = d;
        player.progress = 1 - player.progress;
      }
    };
    for (const [button, d] of Object.entries(DIRECTIONS)) onButtonPress(button, () => request(d));
    // With a mouse or touch: tap where you want to go relative to the player.
    onMousePress(() => {
      const d = mousePos().sub(player.pos);
      request(Math.abs(d.x) > Math.abs(d.y) ? vec2(Math.sign(d.x), 0) : vec2(0, Math.sign(d.y)));
    });
    const choosePlayerDir = (a) => (isFree(a.cell.add(wanted)) ? wanted : a.dir);
  5. Puntos y potenciadores

    Cada punto suma poco y vaciar el laberinto gana la partida. El potenciador invierte los papeles durante unos segundos y obliga a los perseguidores a dar media vuelta, una señal visible de que las reglas cambiaron.

    // Each dot is worth little; the power-up changes the rules for a few seconds.
    player.arrive = () => {
      const key = `${player.cell.x},${player.cell.y}`;
      const p = food.get(key);
      if (!p) return;
      destroy(p);
      food.delete(key);
      score += p.power ? 50 : 10;
      if (p.power) {
        power = POWER_TIME;
        eaten = 0;
        for (const g of ghosts) g.dir = g.dir.scale(-1); // they turn around: a clear sign something changed
      }
      if (food.size === 0) go("end", true, score);
    };
  6. Una IA de persecución legible

    En cada cruce, el perseguidor toma, sin dar media vuelta, la salida que más lo acerca a su objetivo. Lo único que cambia entre ellos es el objetivo, el rojo apunta a tu casilla, el rosa a cuatro casillas por delante de ti y el naranja deambula al azar. Son reglas simples que el jugador puede aprender a leer y a aprovechar, que es la idea que Pac-Man hizo famosa.

    // The AI is readable: at each junction the chaser takes, without turning
    // back, the exit that brings it closest to its target. Only the target
    // changes: red aims at your cell, pink at four cells ahead of you (an
    // ambush) and orange wanders at random. When frightened, they all flee at random.
    const ghosts = [
      ["R", rgb(240, 60, 60), () => player.cell],
      ["S", rgb(255, 150, 200), () => player.cell.add(player.dir.scale(4))],
      ["N", rgb(255, 170, 60), null],
    ].map(([char, tint, target]) =>
      makeActor(findCell(char), [rect(14, 14, { radius: [7, 7, 2, 2] }), color(tint), { tint, target }]),
    );
    function chooseGhostDir(g) {
      let exits = Object.values(DIRECTIONS).filter((d) => isFree(g.cell.add(d)) && !d.eq(g.dir.scale(-1)));
      if (exits.length === 0) exits = [g.dir.scale(-1)]; // dead end: the only option is to go back
      if (power > 0 || !g.target) return choose(exits);
      const goal = g.target();
      return exits.reduce((best, d) => (g.cell.add(d).dist(goal) < g.cell.add(best).dist(goal) ? d : best));
    }
  7. Riesgo y recompensa

    Tocar a un perseguidor cuesta una vida, salvo que esté asustado. Entonces vale cada vez más (200, 400, 800), lo que tienta a ir a por ellos en lugar de seguir comiendo puntos con calma.

    // Risk and reward: touching a chaser costs a life, unless it is
    // frightened; then it is worth more each time (200, 400, 800).
    function resetPositions() {
      for (const a of [player, ...ghosts]) Object.assign(a, { cell: a.home, next: a.home, dir: vec2(0), progress: 1 });
      wanted = vec2(0);
      power = 0;
      pause = 1;
    }
    function checkHits() {
      for (const g of ghosts) {
        if (g.pos.dist(player.pos) > TILE * 0.7) continue;
        if (power > 0) {
          eaten++;
          score += 100 * 2 ** eaten;
          Object.assign(g, { cell: g.home, next: g.home, progress: 1 });
        } else if (--lives <= 0) {
          return go("end", false, score);
        } else {
          return resetPositions();
        }
      }
    }
  8. Escenas

    El juego tiene tres escenas (title, game y end) y go() salta de una a otra. Así se reinicia la partida sin tener que limpiar nada a mano.

    function screen(title, subtitle, next) {
      add([text(title, { size: 34, font: FONT, align: "center" }), pos(center().sub(0, 40)), anchor("center")]);
      add([
        text(subtitle, { size: 16, font: FONT, align: "center", width: 540 }),
        pos(center().add(0, 30)),
        anchor("center"),
        color(200, 210, 255),
      ]);
      onButtonPress("start", next);
      onClick(next);
    }
    
    scene("title", () =>
      screen(
        t({ es: "Laberinto", en: "Maze" }),
        t({
          es: "Cómete todos los puntos sin que te atrapen. Los puntos grandes asustan\na los perseguidores y puedes comértelos. Flechas o toques para girar.\nPulsa espacio o toca para empezar.",
          en: "Eat every dot without getting caught. The big dots scare the chasers\nand let you eat them. Arrows or taps to turn.\nPress space or tap to start.",
        }),
        () => go("game"),
      ),
    );
    
    scene("end", (won, score) =>
      screen(
        won ? t({ es: "¡Laberinto limpio!", en: "Maze cleared!" }) : t({ es: "Te atraparon", en: "You got caught" }),
        t({ es: `Puntos: ${score}. Pulsa espacio para repetir.`, en: `Score: ${score}. Press space to play again.` }),
        () => go("game"),
      ),
    );
    
    go("title");

Qué simplificamos

  • Los perseguidores no alternan entre perseguir y dispersarse por turnos, como en Pac-Man; siempre persiguen, salvo cuando están asustados.
  • No hay casa de fantasmas, túneles laterales, frutas ni niveles que aceleren.
  • Las formas son círculos y rectángulos, sin animaciones ni sonido.

Para modificarla

  • Agrega un cuarto perseguidor que imite al Clyde de Pac-Man, que te persigue si está lejos y se va a una esquina si está cerca.
  • Haz que los perseguidores alternen cada pocos segundos entre perseguir y dispersarse hacia su esquina, y observa cómo cambia la sensación de presión.
  • Diseña otro laberinto en el mapa de texto con un túnel que conecte los lados izquierdo y derecho.
Código completo
// Maze demo
// Engine: Kaplay 3001 (https://kaplayjs.com). MIT licence.
import kaplay from "../../vendor/kaplay.mjs";
import { t, FONT, touchControls, pixelDensity } from "../common.js";

// #region setup
const k = kaplay({
  width: 640,
  height: 360,
  letterbox: true, pixelDensity,
  background: [12, 12, 30],
  // Virtual buttons: the game asks about "up", not about a specific key.
  buttons: {
    left: { keyboard: ["left", "a"] },
    right: { keyboard: ["right", "d"] },
    up: { keyboard: ["up", "w"] },
    down: { keyboard: ["down", "s"] },
    start: { keyboard: ["space", "enter"] },
  },
});
touchControls(k, [
  { button: "left", label: "◀" },
  { button: "right", label: "▶" },
  { button: "up", label: "▲", side: "right" },
  { button: "down", label: "▼" },
]);

const TILE = 18;
const PLAYER_SPEED = 7; // cells per second
const GHOST_SPEED = 6;
const FRIGHTENED_SPEED = 3.5;
const POWER_TIME = 6; // seconds
const DIRECTIONS = { left: vec2(-1, 0), right: vec2(1, 0), up: vec2(0, -1), down: vec2(0, 1) };
// #endregion

// #region maze
// The maze is a text drawing: # wall · . dot · o power-up
// P player · R, S, N the three chasers (red, pink and orange).
const MAP = [
  "#####################",
  "#.........#.........#",
  "#o###.###.#.###.###o#",
  "#...................#",
  "#.###.#.#####.#.###.#",
  "#.....#...#...#.....#",
  "#####.###.#.###.#####",
  "#.........R.........#",
  "#.###.###.#.###.###.#",
  "#...#.....S.....#...#",
  "###.#.#.#####.#.#.###",
  "#.....#...N...#.....#",
  "#.#######.#.#######.#",
  "#.........P.........#",
  "#.###.###.#.###.###.#",
  "#o..#...........#..o#",
  "#.#.#.###.#.###.#.#.#",
  "#...................#",
  "#####################",
];
const X0 = (640 - MAP[0].length * TILE) / 2;
const Y0 = (360 - MAP.length * TILE) / 2;
const isFree = (c) => MAP[c.y]?.[c.x] !== undefined && MAP[c.y][c.x] !== "#";
const cellToScreen = (c) => vec2(X0 + c.x * TILE + TILE / 2, Y0 + c.y * TILE + TILE / 2);
const findCell = (char) => {
  const y = MAP.findIndex((row) => row.includes(char));
  return vec2(MAP[y].indexOf(char), y);
};
// #endregion

// #region movement
// Everyone moves the same way: from cell centre to cell centre. On reaching a
// cell, `choose` picks the next direction. So movement reads as a grid and
// decisions only happen at junctions.
function makeActor(home, comps) {
  const a = add([...comps, pos(cellToScreen(home)), anchor("center")]);
  Object.assign(a, { home, cell: home, next: home, dir: vec2(0), progress: 1 });
  return a;
}
function step(a, speed, chooseDir) {
  a.progress += dt() * speed;
  if (a.progress >= 1) {
    a.cell = a.next;
    a.arrive?.();
    a.dir = chooseDir(a);
    const ahead = a.cell.add(a.dir);
    if (isFree(ahead)) (a.next = ahead), (a.progress = 0);
    else (a.dir = vec2(0)), (a.progress = 1);
  }
  a.pos = cellToScreen(a.cell).lerp(cellToScreen(a.next), Math.min(a.progress, 1));
}
// #endregion

scene("game", () => {
  let score = 0;
  let lives = 3;
  let power = 0; // seconds the chasers stay frightened
  let eaten = 0; // ghosts eaten with the same power-up
  let pause = 1;
  const hud = add([text("", { size: 15, font: FONT, width: 110 }), pos(14, 14)]);

  // Walls and dots come from the map. Dots are stored by cell.
  const food = new Map();
  MAP.forEach((row, y) =>
    [...row].forEach((c, x) => {
      const p = cellToScreen(vec2(x, y));
      if (c === "#") add([rect(TILE, TILE), pos(p), anchor("center"), color(40, 60, 170)]);
      if (c === "." || c === "o")
        food.set(`${x},${y}`, add([circle(c === "o" ? 6 : 2.5), pos(p), anchor("center"), color(255, 220, 170), { power: c === "o" }]));
    }),
  );

  // #region player
  // The player remembers the last direction asked for and takes it as soon as
  // the corridor allows: turning "before" reaching the junction makes the
  // controls feel precise. Turning around is instant.
  const player = makeActor(findCell("P"), [circle(7), color(255, 225, 60)]);
  let wanted = vec2(0);
  const request = (d) => {
    wanted = d;
    if (d.eq(player.dir.scale(-1))) {
      [player.cell, player.next] = [player.next, player.cell];
      player.dir = d;
      player.progress = 1 - player.progress;
    }
  };
  for (const [button, d] of Object.entries(DIRECTIONS)) onButtonPress(button, () => request(d));
  // With a mouse or touch: tap where you want to go relative to the player.
  onMousePress(() => {
    const d = mousePos().sub(player.pos);
    request(Math.abs(d.x) > Math.abs(d.y) ? vec2(Math.sign(d.x), 0) : vec2(0, Math.sign(d.y)));
  });
  const choosePlayerDir = (a) => (isFree(a.cell.add(wanted)) ? wanted : a.dir);
  // #endregion

  // #region eating
  // Each dot is worth little; the power-up changes the rules for a few seconds.
  player.arrive = () => {
    const key = `${player.cell.x},${player.cell.y}`;
    const p = food.get(key);
    if (!p) return;
    destroy(p);
    food.delete(key);
    score += p.power ? 50 : 10;
    if (p.power) {
      power = POWER_TIME;
      eaten = 0;
      for (const g of ghosts) g.dir = g.dir.scale(-1); // they turn around: a clear sign something changed
    }
    if (food.size === 0) go("end", true, score);
  };
  // #endregion

  // #region chasers
  // The AI is readable: at each junction the chaser takes, without turning
  // back, the exit that brings it closest to its target. Only the target
  // changes: red aims at your cell, pink at four cells ahead of you (an
  // ambush) and orange wanders at random. When frightened, they all flee at random.
  const ghosts = [
    ["R", rgb(240, 60, 60), () => player.cell],
    ["S", rgb(255, 150, 200), () => player.cell.add(player.dir.scale(4))],
    ["N", rgb(255, 170, 60), null],
  ].map(([char, tint, target]) =>
    makeActor(findCell(char), [rect(14, 14, { radius: [7, 7, 2, 2] }), color(tint), { tint, target }]),
  );
  function chooseGhostDir(g) {
    let exits = Object.values(DIRECTIONS).filter((d) => isFree(g.cell.add(d)) && !d.eq(g.dir.scale(-1)));
    if (exits.length === 0) exits = [g.dir.scale(-1)]; // dead end: the only option is to go back
    if (power > 0 || !g.target) return choose(exits);
    const goal = g.target();
    return exits.reduce((best, d) => (g.cell.add(d).dist(goal) < g.cell.add(best).dist(goal) ? d : best));
  }
  // #endregion

  // #region risk
  // Risk and reward: touching a chaser costs a life, unless it is
  // frightened; then it is worth more each time (200, 400, 800).
  function resetPositions() {
    for (const a of [player, ...ghosts]) Object.assign(a, { cell: a.home, next: a.home, dir: vec2(0), progress: 1 });
    wanted = vec2(0);
    power = 0;
    pause = 1;
  }
  function checkHits() {
    for (const g of ghosts) {
      if (g.pos.dist(player.pos) > TILE * 0.7) continue;
      if (power > 0) {
        eaten++;
        score += 100 * 2 ** eaten;
        Object.assign(g, { cell: g.home, next: g.home, progress: 1 });
      } else if (--lives <= 0) {
        return go("end", false, score);
      } else {
        return resetPositions();
      }
    }
  }
  // #endregion

  onUpdate(() => {
    hud.text = t({ es: "Puntos\n", en: "Score\n" }) + score + t({ es: "\n\nVidas\n", en: "\n\nLives\n" }) + lives;
    if (pause > 0) return void (pause -= dt());
    power = Math.max(0, power - dt());
    step(player, PLAYER_SPEED, choosePlayerDir);
    for (const g of ghosts) {
      step(g, power > 0 ? FRIGHTENED_SPEED : GHOST_SPEED, chooseGhostDir);
      // Blue while the power lasts; they blink when it is about to run out.
      const blink = power < 2 && Math.floor(time() * 6) % 2;
      g.color = power > 0 && !blink ? rgb(60, 90, 255) : g.tint;
    }
    checkHits();
  });
});

// #region scenes
function screen(title, subtitle, next) {
  add([text(title, { size: 34, font: FONT, align: "center" }), pos(center().sub(0, 40)), anchor("center")]);
  add([
    text(subtitle, { size: 16, font: FONT, align: "center", width: 540 }),
    pos(center().add(0, 30)),
    anchor("center"),
    color(200, 210, 255),
  ]);
  onButtonPress("start", next);
  onClick(next);
}

scene("title", () =>
  screen(
    t({ es: "Laberinto", en: "Maze" }),
    t({
      es: "Cómete todos los puntos sin que te atrapen. Los puntos grandes asustan\na los perseguidores y puedes comértelos. Flechas o toques para girar.\nPulsa espacio o toca para empezar.",
      en: "Eat every dot without getting caught. The big dots scare the chasers\nand let you eat them. Arrows or taps to turn.\nPress space or tap to start.",
    }),
    () => go("game"),
  ),
);

scene("end", (won, score) =>
  screen(
    won ? t({ es: "¡Laberinto limpio!", en: "Maze cleared!" }) : t({ es: "Te atraparon", en: "You got caught" }),
    t({ es: `Puntos: ${score}. Pulsa espacio para repetir.`, en: `Score: ${score}. Press space to play again.` }),
    () => go("game"),
  ),
);

go("title");
// #endregion

Retos de diseño

  • Una IA de persecución que se sienta amenazante pero legible, para que el jugador pueda anticiparla y burlarla en vez de sentir que lo atrapan al azar.
  • Diseñar el laberinto con suficientes cruces y rutas de escape para que no haya trampas sin salida, y repartir los potenciadores para crear decisiones de riesgo y recompensa.
  • Un control de giro que perdone, que recuerde la dirección pedida y la tome en cuanto el pasillo lo permita.
  • Subir la dificultad (velocidad, agresividad de los perseguidores, duración del potenciador) sin cambiar las reglas que el jugador ya aprendió.

Historia

  1. 1973
    OrigenGotcha

    Antes hubo laberintos en computadoras de investigación, como Mouse in the Maze en los años cincuenta o Maze (1973), en 3D; Gotcha fue el primer juego de laberinto de arcade.

    Maze game (Wikipedia)
  2. 1979
    Head On

    Head On es el primer laberinto en el que el objetivo es recoger todos los puntos esquivando a un rival.

    Head On (video game) (Wikipedia)
  3. 1980
    Pac-Man

    Pac-Man da a cada fantasma su propia forma de perseguir y añade potenciadores que invierten la caza. Se vuelve el modelo del género.

    Pac-Man (Wikipedia)
  4. 1981
    3D Monster Maze

    3D Monster Maze lleva el laberinto a la primera persona en una computadora doméstica, con un monstruo que te caza.

    3D Monster Maze (Wikipedia)
  5. 1982

    En el caso Atari contra North American Philips, la justicia de EE. UU. frena el clon K.C. Munchkin!, pero trata la idea de perseguir en un laberinto como de uso libre. Por eso abundan los juegos de este tipo.

    Maze game (Wikipedia)
  6. 2007
    Pac-Man Championship Edition

    Pac-Man Championship Edition renueva la fórmula con límite de tiempo, mitades del laberinto que se regeneran y velocidad creciente.

    Pac-Man Championship Edition (Wikipedia)
  7. 2010
    Pac-Man

    Por los 30 años de Pac-Man, Google pone en su portada una versión jugable que sigue disponible en el navegador.

    Pac-Man (Wikipedia)

Juegos de referencia

1973

Gotcha

Atari (Allan Alcorn). arcade

Dos jugadores recorren un laberinto que cambia sin parar; uno persigue y el otro huye. Se considera el primer juego de laberinto de arcade.

1979

Head On

Sega/Gremlin (Lane Hauck). arcade

Conduces un coche por carriles concéntricos recogiendo puntos mientras otro coche viene en sentido contrario; solo puedes cambiar de carril en ciertos huecos. Fue el primer laberinto en el que el objetivo es pasar por encima de los puntos.

1981

3D Monster Maze

Malcolm Evans (J. K. Greye Software). ZX81

Buscas en primera persona la salida de un laberinto generado al azar mientras te acecha un tiranosaurio. Eres más rápido que él, pero un callejón sin salida puede ser el final.

2007

Pac-Man Championship Edition

Namco Bandai Games. Xbox 360

Una vuelta a Pac-Man con límite de tiempo, en la que cada mitad del laberinto se renueva al vaciarla y el juego acelera según sube tu puntuación.

2022

Captain Neat-o in Treasure Maze

David Couzelis. PICO-8, navegador

Recorres un laberinto recogiendo tesoros y esquivando serpientes guardianas hasta llegar a la salida. Es un juego corto, de unos tres minutos, hecho en PICO-8 para la Toy Box Jam 3.

Ejercicio

Juega la demo y describe con palabras cómo se mueve cada perseguidor. Luego cambia en el código el objetivo del rosa (por ejemplo, ocho casillas por delante en lugar de cuatro) y comenta si la emboscada se vuelve más o menos peligrosa.

Subgéneros

  • Persecución en laberintoRecorres un laberinto que cabe en una pantalla recogiendo todos los puntos mientras te persiguen enemigos por los mismos pasillos; ganas leyendo sus rutas y usando los objetos que, por un rato, te dejan darles la vuelta.

Fuentes