<|start_filename|>docs/boarding/main.js<|end_filename|>
title = "BOARDING";

description = `
[Hold] Boarding
`;

characters = [
  `
 lll
l l ll
 llll
 l  l
ll  ll
  `,
  `
 lll
l l ll
 llll
  ll
 l  l
 l  l
  `,
  `
llllll
ll l l
ll l l
llllll
 l  l
 l  l
    `,
  `
llllll
ll l l
ll l l
llllll
ll  ll
    `,
  `
     l
    ll
   ll
  ll
 ll
ll
`,
  `
l
ll
 ll
  ll
   ll
    ll
`,
  `
  l
 l
lllll
 l
  l
`,
  `
  l
   l
lllll
   l
  l
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 6,
};

let boards;
let passengers;
let px, pt, pi, pvx;
let gy;
let tgy;
let bi;
let scr;
let isFirstPressing;

function update() {
  if (!ticks) {
    boards = [];
    passengers = [];
    px = 50;
    pt = -1;
    pi = 0;
    pvx = -1;
    gy = 50;
    bi = 0;
    scr = 0.1;
    isFirstPressing = true;
    while (gy < 91) {
      addBoard();
      gy += 2;
    }
    gy = tgy = 91;
  }
  color("black");
  boards = boards.filter((b) => {
    b.pos.y -= scr * difficulty;
    char(addWithCharCode("e", b.angle), b.pos);
    return b.pos.y > 15 && b.pos.y < gy;
  });
  tgy -= scr * difficulty;
  if (tgy < 11) {
    tgy = 11;
  }
  gy += (tgy - gy) * 0.1;
  color("red");
  rect(0, gy, 50, 8);
  color("blue");
  rect(50, gy, 50, 8);
  color("white");
  char("a", 5, gy + 4);
  char("g", 12, gy + 4);
  text("GATE1", 20, gy + 4);
  char("c", 95, gy + 4);
  char("h", 88, gy + 4);
  text("GATE2", 55, gy + 4);
  if (bi < 0) {
    addBoard();
    bi += 4;
  }
  color(pt < 0 ? "red" : "blue");
  char(addWithCharCode("a", pt + 1 + (floor(ticks / 30) % 2)), px, 9);
  if (gy <= 12) {
    play("explosion");
    end();
  }
  pi--;
  let speed = 1;
  if (input.isPressed && !isFirstPressing) {
    if (pi < 0) {
      const p = {
        pos: vec(px, 9),
        vel: vec(),
        type: pt,
        prevPos: vec(),
        bc: 0,
      };
      p.prevPos.set(p.pos);
      passengers.push(p);
      pi = 9;
    }
    speed = 0.1;
  }
  px += pvx * difficulty * speed;
  if ((px < 10 && pvx < 0) || (px > 90 && pvx > 0)) {
    pvx *= -1;
  }
  if (input.isJustReleased) {
    if (isFirstPressing) {
      isFirstPressing = false;
    } else {
      pt *= -1;
    }
  }
  passengers = passengers.filter((p) => {
    p.vel.y += 0.2;
    p.vel.mul(0.9);
    p.prevPos.set(p.pos);
    p.pos.add(p.vel);
    color(p.type < 0 ? "red" : "blue");
    const c = char(
      addWithCharCode("a", p.type + 1 + (floor(ticks / 30) % 2)),
      p.pos
    ).isColliding.char;
    if (c.e || c.f) {
      const a = wrap(p.pos.angleTo(p.prevPos), -PI, PI);
      let ra;
      if (c.e) {
        ra = a < -PI / 4 || a > (PI / 4) * 3 ? (-PI / 4) * 3 : PI / 4;
      } else {
        ra = a < (-PI / 4) * 3 || a > PI / 4 ? (PI / 4) * 3 : -PI / 4;
      }
      const v = vec(p.vel.length * 2).rotate(ra);
      p.vel.add(v);
      p.pos.add(p.vel);
      p.pos.add(p.vel);
      p.bc++;
    }
    if (p.pos.y < 0 && p.vel.y < 0) {
      p.vel.y *= -0.5;
    }
    if ((p.pos.x < 0 && p.vel.x < 0) || (p.pos.x > 99 && p.vel.x > 0)) {
      p.vel.x *= -1;
    }
    if (p.pos.y > gy) {
      const isOk = (p.pos.x - 50) * p.type > 0;
      if (isOk) {
        if (p.bc > 0) {
          play("powerUp");
          addScore(p.bc, p.pos);
          const oy = p.bc * 2 * difficulty;
          tgy += oy;
          bi -= oy;
          if (tgy > 91) {
            tgy = 91;
          }
        } else {
          addScore(0, p.pos);
        }
        return false;
      } else {
        play("hit");
        addScore(-1 - p.bc, p.pos);
        let oy = sqrt(1 + p.bc) * difficulty;
        if (oy > 20) {
          oy = 20;
        }
        tgy -= oy;
        return false;
      }
    }
    return true;
  });
}

function addBoard() {
  const pos = vec(rnd(9, 90), gy - 3);
  if (boards.some((b) => b.pos.distanceTo(pos) < 16)) {
    return;
  }
  boards.push({
    pos,
    angle: rndi(2),
  });
}


<|start_filename|>docs/numberball/main.js<|end_filename|>
title = "NUMBER BALL";

description = `
[Hold]    Set angle
[Release] Hit a shot
`;

characters = [];

options = {
  viewSize: { x: 200, y: 60 },
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 8,
};

/**
 * @type {{
 * value: number, pos: Vector, vel: Vector,
 * state: "stay" | "flying" | "onFloor" | "removing" | "falling"
 * }[]}
 */
let balls;
let isAddingNextBall;
/** @type {{value: number, x: number, width: number}[]} */
let floors;
let multiplier;
let scrV;
let scrVB;

function update() {
  if (!ticks) {
    balls = [];
    isAddingNextBall = true;
    floors = [{ value: undefined, x: 0, width: 200 }];
    multiplier = 1;
    scrV = scrVB = 0;
  }
  const lf = floors[0];
  const lx = lf.x + lf.width;
  let scr = sqrt(difficulty) * 0.04 + scrV;
  scrV *= 0.9;
  scrVB = clamp(scrVB - 0.01, 0, 9);
  if (lx > 100) {
    scr += (lx - 100) * 0.1;
  }
  const ballMaxValue = clamp(difficulty * 2, 1, 9);
  let rx = 0;
  remove(floors, (f, i) => {
    f.x -= scr;
    color(i == 0 ? "light_red" : "light_green");
    rect(f.x, 50, f.width - 2, 10);
    rx = f.x + f.width;
    if (f.value != null) {
      color("red");
      text(`${f.value}`, f.x + 7, 55);
      let isRemoving = false;
      balls.forEach((b) => {
        if (
          b.state === "onFloor" &&
          b.pos.x > f.x - 3 &&
          b.pos.x < rx + 3 &&
          f.value === b.value
        ) {
          isRemoving = true;
        }
      });
      if (isRemoving) {
        play("coin");
        addScore(f.value * multiplier, f.x + 7, 50);
        if (multiplier < 16) {
          multiplier *= 2;
        }
        return true;
      }
    }
    if (i < floors.length - 1) {
      const rf = floors[i + 1];
      if (rx < rf.x + 2) {
        f.x += (rf.x - rx) * 0.1;
      } else {
        f.x = rf.x - f.width;
      }
    }
  });
  color("green");
  rect(0, 50, 20, 10);
  color("white");
  rect(20, 50, 13, 10);
  if (floors[0].x + floors[0].width < 30) {
    color("red");
    text("X", 33, 55);
    play("explosion");
    end();
  }
  if (rx < 200) {
    play("hit");
    floors.push({
      value: rndi(floor(ballMaxValue)) + 1,
      x: 200,
      width: rnd(20, 50),
    });
  }
  if (ticks > 30 && isAddingNextBall) {
    let value;
    for (let i = 0; i < 9; i++) {
      value = rndi(floor(ballMaxValue)) + 1;
      let isMatched = false;
      floors.forEach((f) => {
        if (f.value === value) {
          isMatched = true;
        }
      });
      if (isMatched) {
        break;
      }
    }
    play("laser");
    balls.push({
      value,
      pos: vec(10, 47),
      vel: vec(1, 0).rotate(-0.1),
      state: "stay",
    });
    isAddingNextBall = false;
  }
  remove(balls, (b) => {
    if (b.state === "removing") {
      return true;
    }
    if (b.state === "stay") {
      if (input.isJustPressed) {
        play("select");
      }
      if (input.isPressed) {
        b.vel.rotate(-0.01 * difficulty);
        color("black");
        line(vec(b.vel).mul(3).add(b.pos), vec(b.vel).mul(15).add(b.pos), 2);
      }
      if (input.isJustReleased || b.vel.angle < -PI * 0.47) {
        play("powerUp");
        if (multiplier > 1) {
          multiplier /= 2;
        }
        scrV = scrVB;
        scrVB++;
        b.vel.mul(5);
        b.state = "flying";
      }
    } else if (b.state === "flying") {
      b.vel.y += 0.1;
      b.vel.mul(0.99);
      b.pos.add(b.vel);
      if (b.pos.y > 47) {
        b.vel.y *= -0.5;
        b.vel.x *= 0.5;
        b.pos.y = 47;
        if (abs(b.vel.y) < 0.5) {
          b.state = "onFloor";
          isAddingNextBall = true;
        }
      }
    } else if (b.state === "onFloor") {
      b.vel.x *= 0.9;
      b.pos.x += b.vel.x;
      b.vel.y = 0;
      if (b.pos.x < 30) {
        b.state = "falling";
      }
    } else if (b.state === "falling") {
      b.vel.y += 0.1;
      b.pos.y += b.vel.y;
      if (b.pos.y > 63) {
        return true;
      }
    }
    if (b.state !== "stay" && b.state !== "falling") {
      b.pos.x -= scr;
      balls.forEach((ob) => {
        if (ob === b) {
          return;
        }
        if (ob.pos.distanceTo(b.pos) < 6) {
          b.vel.addWithAngle(ob.pos.angleTo(b.pos), ob.vel.length * 0.7);
          ob.vel.addWithAngle(b.pos.angleTo(ob.pos), b.vel.length * 0.7);
        }
      });
    }
    color("blue");
    text(`${b.value}`, b.pos);
    if (b.pos.x > 220 || b.pos.x < -20 || b.pos.y < -50) {
      if (b.state === "flying") {
        isAddingNextBall = true;
      }
      return true;
    }
  });
  color("black");
  text(`x${multiplier}`, 3, 10);
}


<|start_filename|>docs/bombup/main.js<|end_filename|>
title = "BOMB UP";

description = `
[Tap]
 Throw
 Blast
`;

characters = [
  `
llllll
ll l l
ll l l
llllll
 l  l
 l  l
  `,
  `
llllll
ll l l
ll l l
llllll
ll  ll
  `,
  `
  r
 r
 ll
llll
llll
 ll
  `,
];

options = {
  theme: "dark",
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 90,
};

/** @type {{pos: Vector, vel: Vector}} */
let player;
/** @type {{pos: Vector, vel: Vector}} */
let bomb;
/** @type {{pos: Vector, ticks: number}} */
let explosion;
/** @type {{pos: Vector, vel: Vector}[]} */
let rocks;
let nextRockTicks;
let nextRockX;
let nextRockVelX;
let wallY;
let multiplier;

function update() {
  if (!ticks) {
    player = { pos: vec(50, 50), vel: vec() };
    bomb = explosion = undefined;
    rocks = [];
    nextRockTicks = 0;
    nextRockX = 80;
    nextRockVelX = 0;
    wallY = 0;
    multiplier = 1;
  }
  let scr = 0;
  if (player.pos.y < 40) {
    scr = (40 - player.pos.y) * 0.3;
  } else if (player.pos.y > 60) {
    scr = (60 - player.pos.y) * 0.2;
  }
  player.vel.y += 0.02;
  if (
    (player.pos.x < 7 && player.vel.x < 0) ||
    (player.pos.x > 93 && player.vel.x > 0)
  ) {
    player.vel.x *= -0.7;
    player.pos.x = clamp(player.pos.x, 7, 93);
  }
  player.vel.mul(0.99);
  player.pos.add(player.vel);
  player.pos.y += scr;
  color("black");
  char(player.vel.y < 0 ? "a" : "b", player.pos, {
    mirror: { x: player.vel.x < 0 ? -1 : 1 },
  });
  if (bomb == null) {
    char("c", player.pos.x + (player.vel.x < 0 ? -2 : 2), player.pos.y);
  }
  if (input.isJustPressed) {
    if (bomb == null) {
      play("select");
      bomb = {
        pos: vec(player.pos.x + (player.vel.x < 0 ? -2 : 2), player.pos.y),
        vel: vec(player.vel.x + (player.vel.x < 0 ? -0.5 : 0.5), player.vel.y),
      };
      multiplier = 1;
    } else if (bomb != null) {
      play("powerUp");
      explosion = { pos: vec(bomb.pos), ticks: 0 };
      color("light_red");
      particle(bomb.pos, 20, 3);
      const d = bomb.pos.distanceTo(player.pos);
      const a = bomb.pos.angleTo(player.pos);
      player.vel.addWithAngle(a, 20 / d);
      bomb = undefined;
    }
  }
  if (bomb != null) {
    if (
      (bomb.pos.x < 7 && bomb.vel.x < 0) ||
      (bomb.pos.x > 93 && bomb.vel.x > 0)
    ) {
      bomb.vel.x *= -0.7;
    }
    bomb.vel.y += 0.15;
    bomb.vel.mul(0.98);
    bomb.pos.add(bomb.vel);
    bomb.pos.y += scr;
    char("c", bomb.pos);
    if (bomb.pos.y > 103) {
      bomb = undefined;
    }
  }
  if (explosion != null) {
    explosion.ticks++;
    const r = sin(explosion.ticks * 0.15) * 25;
    if (r < 0) {
      explosion = undefined;
    } else {
      color("light_red");
      arc(explosion.pos, r);
    }
  }
  nextRockTicks--;
  nextRockVelX += rnds(0.1) * sqrt(difficulty);
  nextRockVelX *= 0.99;
  nextRockX += nextRockVelX;
  if (
    (nextRockX < 7 && nextRockVelX < 0) ||
    (nextRockX > 93 && nextRockVelX > 0)
  ) {
    nextRockVelX *= -0.7;
    nextRockX = clamp(nextRockX, 7, 93);
  }
  if (nextRockTicks < 0) {
    const isBottom = rnd() > (player.pos.y > 30 ? 0.05 : 0.9);
    rocks.push({
      pos: vec(nextRockX, isBottom ? 103 : -3),
      vel: vec(0, isBottom ? -rnd(0.5, sqrt(difficulty)) : player.vel.y / 2),
    });
    nextRockTicks = rnd(8, 10) / difficulty;
  }
  color("red");
  remove(rocks, (r) => {
    r.pos.add(r.vel);
    r.vel.y += 0.01;
    r.vel.mul(0.99);
    r.pos.add(r.vel);
    r.pos.y += scr;
    particle(r.pos, 0.1, r.vel.length * -0.5, r.vel.angle);
    const c = box(r.pos, 5).isColliding;
    if (c.rect.light_red) {
      play("coin");
      addScore(multiplier, r.pos);
      multiplier++;
      particle(r.pos);
      return true;
    } else if (c.char.a || c.char.b) {
      play("explosion");
      end();
    }
    if (r.pos.y < -50 || r.pos.y > 150) {
      return true;
    }
  });
  wallY += scr;
  color("light_black");
  times(18, (i) => {
    box(3, i * 6 + (wallY % 6), 4);
    box(97, i * 6 + (wallY % 6), 4);
  });
}


<|start_filename|>docs/tarutobi/main.js<|end_filename|>
title = "TARUTOBI";

description = `
[Slide] Move
`;

characters = [
  `
llllll
ll l l
ll l l
llllll
 l  l
 l  l
  `,
  `
llllll
ll l l
ll l l
llllll
ll  ll
  `,
  `
  ll
 llll
l    l
l    l
 l  l
  ll
  `,
];

options = {
  viewSize: { x: 120, y: 60 },
  isPlayingBgm: true,
  isReplayEnabled: true,
};

let p, v, isJumping, ts, addedScore;

function update() {
  if (ticks === 0) {
    p = vec(30, 30);
    v = vec();
    isJumping = true;
    addedScore = 1;
    ts = [];
  }
  if (rnd() < 0.01 * difficulty) {
    ts.push({
      p: vec(123, 47),
      v: vec(-rnd(0.4, 0.8 * difficulty), 0),
      t: 0,
      isScoreAdded: false,
    });
  }
  ts = ts.filter((t) => {
    t.p.add(t.v);
    if (isJumping) {
      text("*", t.p.x, t.p.y, { rotation: floor(t.t / 10) });
    } else {
      char("c", t.p.x, t.p.y, { rotation: floor(t.t / 10) });
    }
    t.t += t.v.x;
    if (!t.isScoreAdded && t.p.x < p.x) {
      play("coin");
      addScore(addedScore, t.p);
      addedScore++;
      t.isScoreAdded = true;
    }
    return t.p.x > 0;
  });
  rect(0, 50, 120, 9);
  if (isJumping) {
    v.y += 0.1;
    if (p.y > 47) {
      isJumping = false;
      p.y = 47;
      v.y = 0;
    }
  } else {
    v.mul(0.95);
    addedScore = 1;
  }
  if (p.x < 0 || p.x > 120) {
    p.x = clamp(p.x, 0, 120);
    v.x = 0;
  }
  v.x += clamp(input.pos.x - p.x, -7, 7) * (isJumping ? 0.004 : 0.01);
  p.add(v);
  const c = char(
    String.fromCharCode("a".charCodeAt(0) + (floor(ticks / 30) % 2)),
    p
  );
  if (c.isColliding.char.c) {
    play("jump");
    isJumping = true;
    p.y = 42;
    v.y = -1.2 - Math.abs(v.x);
  }
  if (c.isColliding.text["*"]) {
    play("explosion");
    end();
  }
}


<|start_filename|>docs/notturn/main.js<|end_filename|>
title = "NOT TURN";

description = `
[Hold]
 Turn & Speed up
`;

characters = [
  `
llllll
ll l l
ll l l
llllll
 l  l
 l  l
    `,
  `

llllll
ll l l
ll l l
llllll
ll  ll
    `,
  `
llllll
l ll l
l ll l
llllll
 l  l
 l  l
    `,
  `

llllll
l ll l
l ll l
llllll
 l  l
    `,
  `
  lll
ll l l
 llll
  ll
 l  l
 l  l
  `,
  `

  lll
ll l l
 llll
 l  l
ll  ll
  `,
  `
 llll
l ll l
 llll
  ll
 l  l
 l  l
  `,
  `

 llll
l ll l
 llll
  ll
 l  l
  `,
  `
  ll
 l ll
 llll
  ll
  `,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  isDrawingScoreFront: true,
  seed: 80,
};

/** @type {{pos: Vector}[]} */
let bars;
let nextBarDist;
/**
 * @type {{
 * pos: Vector, vel: Vector, xIndex: number, noTurnDist: number, dotDist: number
 * }[]}
 */
let enemies;
let nextEnemyTicks;
/**
 * @type {{
 * pos: Vector, vel: Vector, xIndex: number, noTurnDist: number, speed: number
 * }}
 */
let player;
let dots;
let multiplier;
const xBarCount = 5;
const xBarInterval = 20;
const xBarPadding = (100 - (xBarCount - 1) * xBarInterval) / 2;

function update() {
  if (!ticks) {
    bars = [];
    nextBarDist = 0;
    enemies = [];
    nextEnemyTicks = 20;
    const xIndex = 2;
    player = {
      pos: vec(xBarPadding + xBarInterval * xIndex, 99),
      vel: vec(0, 1),
      xIndex,
      noTurnDist: 5,
      speed: 1,
    };
    dots = [];
    multiplier = 1;
  }
  let scr;
  if (player.pos.y > 20) {
    scr = (player.pos.y - 20) * 0.1;
  }
  nextBarDist -= scr;
  if (nextBarDist < 0) {
    let i1 = rndi(xBarCount - 1);
    let i2 = rndi(xBarCount - 1);
    bars.push({
      pos: vec(calcBarX(i1) + xBarInterval / 2, 102),
    });
    if (abs(i2 - i1) > 1) {
      bars.push({
        pos: vec(calcBarX(i2) + xBarInterval / 2, 102),
      });
    }
    nextBarDist += rnd(9, 12);
  }
  color("light_black");
  times(xBarCount, (i) => {
    box(calcBarX(i), 50, 3, 100);
  });
  color("light_blue");
  remove(bars, (b) => {
    b.pos.y -= scr;
    box(b.pos, xBarInterval - 3, 3);
    return b.pos.y < -1;
  });
  const sp = clamp(sqrt(difficulty) * player.speed * 0.2, 0, 3);
  player.pos.add(vec(player.vel).mul(sp));
  player.noTurnDist -= player.vel.y * sp;
  let pc;
  if (player.vel.x !== 0) {
    const bx = calcBarX(player.xIndex);
    if ((player.pos.x - bx) * player.vel.x > 0) {
      player.pos.x = bx;
      player.vel.set(0, 1);
    }
    pc = addWithCharCode("a", floor(ticks / (input.isPressed ? 10 : 20)) % 2);
  } else {
    if (player.noTurnDist < 0 && input.isPressed) {
      color("transparent");
      if (
        box(player.pos.x + 6, player.pos.y + 3, 1, 1).isColliding.rect
          .light_blue
      ) {
        play("select");
        player.vel.set(1, 0);
        player.xIndex++;
        player.noTurnDist = 5;
      } else if (
        box(player.pos.x - 6, player.pos.y + 3, 1, 1).isColliding.rect
          .light_blue
      ) {
        play("select");
        player.vel.set(-1, 0);
        player.xIndex--;
        player.noTurnDist = 5;
      }
    }
    pc = addWithCharCode("c", floor(ticks / (input.isPressed ? 10 : 20)) % 2);
  }
  if (input.isJustPressed) {
    play("laser");
  }
  if (input.isJustReleased) {
    play("hit");
  }
  player.speed += ((input.isPressed ? 4 : 1) - player.speed) * 0.2;
  color("black");
  char(pc, player.pos, { mirror: { x: player.vel.x > 0 ? 1 : -1 } });
  player.pos.y -= scr;
  nextEnemyTicks--;
  if (nextEnemyTicks < 0) {
    const xIndex = rndi(xBarCount);
    enemies.push({
      pos: vec(calcBarX(xIndex), 103),
      vel: vec(0, -1),
      xIndex,
      noTurnDist: 0,
      dotDist: 0,
    });
    nextEnemyTicks = rnd(100, 150) / difficulty;
  }
  remove(enemies, (e) => {
    const sp = clamp(sqrt(difficulty) * 0.3, 0, 3);
    e.pos.add(vec(e.vel).mul(sp));
    e.noTurnDist += e.vel.y * sp;
    if (e.vel.x !== 0) {
      const bx = calcBarX(e.xIndex);
      if ((e.pos.x - bx) * e.vel.x > 0) {
        e.pos.x = bx;
        e.vel.set(0, -1);
      }
    } else if (e.noTurnDist < 0) {
      color("transparent");
      if (box(e.pos.x + 6, e.pos.y + 3, 1, 1).isColliding.rect.light_blue) {
        e.vel.set(1, 0);
        e.xIndex++;
        e.noTurnDist = 5;
      } else if (
        box(e.pos.x - 6, e.pos.y + 3, 1, 1).isColliding.rect.light_blue
      ) {
        e.vel.set(-1, 0);
        e.xIndex--;
        e.noTurnDist = 5;
      }
    }
    color("red");
    const c = char("e", e.pos).isColliding.char;
    if (c.a || c.b || c.c || c.d) {
      play("explosion");
      end();
    }
    e.pos.y -= scr;
    e.dotDist -= sp;
    if (e.dotDist < 0) {
      e.dotDist += 6;
      dots.push({ pos: vec(e.pos) });
    }
    if (e.pos.y < -3) {
      if (multiplier > 1) {
        multiplier--;
      }
      return true;
    }
  });
  color("yellow");
  remove(dots, (d) => {
    const c = char("i", d.pos).isColliding.char;
    if (c.a || c.b || c.c || c.d) {
      play("coin");
      addScore(multiplier, d.pos);
      multiplier++;
      return true;
    }
    d.pos.y -= scr;
    return d.pos.y < -3;
  });
  color("black");
  text(`x${multiplier}`, 3, 10);

  function calcBarX(i) {
    return i * xBarInterval + xBarPadding;
  }
}


<|start_filename|>docs/jumpon/main.js<|end_filename|>
title = "JUMP ON";

description = `
[Tap] Jump on
`;

characters = [
  `
llllll
ll l l
ll l l
llllll
 l  l
 l  l
  `,
  `
llllll
ll l l
ll l l
llllll
ll  ll
  `,
  `
llllll
l ll l
l ll l
llllll
 l  l
 l  l
  `,
  `
  lll
ll l l
 llll
  ll
 l  l
 l  l
`,
  `
  lll
ll l l
 llll
 l  l
ll  ll
`,
  `
 llll
l ll l
 llll
  ll
 l  l
 l  l
`,
  `


  ll
 l ll
 llll
  ll
`,
  `


llllll


`,
  `


ll  ll
  ll

`,
  `


l    l
 l  l
  ll
`,
  `

  ll
ll  ll


`,
  `
  ll
 l  l
l    l


`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  isDrawingScoreFront: true,
  seed: 1,
};

/**
 * @type {{
 * pos: Vector, hole:any, nextDotsDist: number,
 * state: "up" | "down" | "walk" | "jumpTo" | "jumpFrom"
 * }[]}
 * */
let enemies;
let groupEnemyCount;
let nextGroupTicks;
let nextEnemyTicks;
/** @type {{x: number, animTicks: number}[]} */
let holes;
let nextHoleDist;
/**
 * @type {{
 * pos: Vector, hole:any,
 * state: "up" | "down" | "walk" | "jumpTo" | "jumpFrom"
 * }}
 * */
let player;
/** @type {{pos: Vector}[]} */
let dots;
let multiplier;

function update() {
  if (!ticks) {
    enemies = [];
    groupEnemyCount = 0;
    nextGroupTicks = 0;
    nextEnemyTicks = 0;
    holes = [
      { x: 30, animTicks: 99 },
      { x: 60, animTicks: 99 },
    ];
    nextHoleDist = 0;
    player = { pos: vec(5, 87), hole: undefined, state: "walk" };
    dots = [];
    multiplier = 1;
  }
  color("light_yellow");
  rect(0, 90, 100, 10);
  times(5, (i) => {
    rect(0, 90 - (i + 1) * 12, 100, 3);
  });
  color("light_purple");
  rect(0, 10, 100, 10);
  let scr = difficulty * 0.03;
  if (player.pos.x > 15) {
    scr += (player.pos.x - 15) * 0.1;
  }
  nextHoleDist -= scr;
  if (nextHoleDist < 0) {
    holes.push({ x: 104, animTicks: 99 });
    nextHoleDist += rnd(30, 45);
  }
  remove(holes, (h) => {
    h.x -= scr;
    color("white");
    box(h.x, 60, 8, 80);
    color("black");
    h.animTicks += difficulty;
    const ai =
      h.animTicks < 21 ? [1, 2, 1, 0, 3, 4, 3][floor(h.animTicks / 3)] : 0;
    char(addWithCharCode("h", ai), h.x, 90);
    return h.x < -4;
  });
  nextGroupTicks--;
  if (nextGroupTicks < 0) {
    nextEnemyTicks = 0;
    groupEnemyCount = rndi(1, 1 + round(sqrt(difficulty) * 2));
    nextGroupTicks = (groupEnemyCount * 8 + rnd(100, 110)) / difficulty;
  }
  nextEnemyTicks--;
  if (nextEnemyTicks < 0) {
    enemies.push({
      pos: vec(103, 87 - rndi(6) * 12),
      hole: undefined,
      state: "walk",
      nextDotsDist: rnd(6),
    });
    groupEnemyCount--;
    if (groupEnemyCount === 0) {
      nextEnemyTicks = 9999;
    } else {
      nextEnemyTicks = rnd(8, 10) / difficulty;
    }
  }
  color("red");
  remove(enemies, (e) => {
    if (e.state === "walk") {
      e.pos.x += -difficulty - scr;
      holes.forEach((h) => {
        const o = e.pos.x - h.x;
        if (o < 8 && o > 0) {
          play("laser");
          e.state = "jumpTo";
          e.hole = h;
        }
      });
      char("e", e.pos, { mirror: { x: -1 } });
    } else if (e.state === "jumpTo") {
      e.pos.x += -difficulty - scr;
      const o = e.pos.x - e.hole.x;
      e.pos.y += o < 4 ? 1 : -1;
      if (o <= 0) {
        e.state = "down";
      }
      char("d", e.pos, { mirror: { x: -1 } });
    } else if (e.state === "down") {
      e.pos.x = e.hole.x;
      e.pos.y += difficulty;
      if (e.pos.y > 90) {
        play("hit");
        e.state = "up";
        e.hole.animTicks = 0;
      }
      char("f", e.pos);
    } else if (e.state === "up") {
      e.pos.x = e.hole.x;
      e.pos.y -= difficulty;
      if (e.pos.y < 23) {
        e.state = "down";
      }
      const o = e.pos.y - player.pos.y;
      if (o < 1 && o > -9) {
        e.state = "jumpFrom";
        e.pos.y = ceil((e.pos.y - 90) / 12) * 12 + 87;
      }
      char("f", e.pos);
    } else if (e.state === "jumpFrom") {
      e.pos.x += -difficulty - scr;
      const o = e.hole.x - e.pos.x;
      e.pos.y += o < 4 ? -1 : 1;
      if (o >= 8) {
        play("laser");
        e.state = "walk";
        e.pos.y = round((e.pos.y - 90) / 12) * 12 + 87;
        multiplier = 1;
      }
      char("d", e.pos, { mirror: { x: -1 } });
    }
    if (e.state === "walk") {
      e.nextDotsDist -= difficulty;
      if (e.nextDotsDist < 0) {
        dots.push({ pos: vec(e.pos) });
        e.nextDotsDist += 6;
      }
    }
    return e.pos.x < -3;
  });
  color("black");
  if (player.state === "walk") {
    player.pos.x += difficulty - scr;
    holes.forEach((h) => {
      const o = h.x - player.pos.x;
      if (o < 8 && o > 0) {
        play("jump");
        player.state = "jumpTo";
        player.hole = h;
      }
    });
    const c = char(addWithCharCode("a", floor(ticks / 10) % 2), player.pos)
      .isColliding.char;
    if (c.e) {
      play("explosion");
      end();
    }
  } else if (player.state === "jumpTo") {
    player.pos.x += difficulty - scr;
    const o = player.hole.x - player.pos.x;
    player.pos.y += o < 4 ? 1 : -1;
    if (o <= 0) {
      player.state = "down";
    }
    char("a", player.pos);
  } else if (player.state === "down") {
    player.pos.x = player.hole.x;
    player.pos.y += difficulty;
    if (player.pos.y > 90) {
      play("powerUp");
      player.state = "up";
      player.hole.animTicks = 0;
    }
    char("c", player.pos);
  } else if (player.state === "up") {
    player.pos.x = player.hole.x;
    player.pos.y -= difficulty;
    if (player.pos.y < 23) {
      player.state = "down";
    }
    if (input.isPressed) {
      play("jump");
      player.state = "jumpFrom";
      player.pos.y = ceil((player.pos.y - 90) / 12) * 12 + 87;
    }
    char("c", player.pos);
  } else if (player.state === "jumpFrom") {
    player.pos.x += difficulty - scr;
    const o = player.pos.x - player.hole.x;
    player.pos.y += o < 4 ? -1 : 1;
    if (o >= 8) {
      player.state = "walk";
      player.pos.y = round((player.pos.y - 90) / 12) * 12 + 87;
    }
    char("a", player.pos);
  }
  if (player.pos.x < 0) {
    play("explosion");
    end();
  }
  color("yellow");
  remove(dots, (d) => {
    d.pos.x -= scr;
    const c = char("g", d.pos).isColliding.char;
    if (c.a || c.b) {
      play("coin");
      addScore(multiplier, d.pos);
      multiplier++;
      return true;
    }
    return d.pos.x < -3;
  });
}


<|start_filename|>docs/aerialbar/main.js<|end_filename|>
title = "AERIAL BAR";

description = `
[Tap]  Jump
[Hold] Fly
`;

characters = [];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 9000,
};

/**
 * @type {{
 * x: number, length: number, angle: number, angleVel: number, isHeld: boolean
 * }[]}
 */
let bars;
let nextBarDist;
/**
 * @type {{
 * pos: Vector, length: number, angle: number, angleVel: number,
 * center: number, bar: any, vel: Vector,
 * }}
 */
let player;
let flyingTicks;
let ceilingY;
let targetCeilingY;

function update() {
  if (!ticks) {
    bars = [{ x: 50, length: 50, angle: PI / 2, angleVel: 0.03, isHeld: true }];
    nextBarDist = 0;
    player = {
      pos: vec(),
      length: 10,
      angle: 0,
      angleVel: 0,
      center: 0.2,
      bar: bars[0],
      vel: vec(),
    };
    flyingTicks = 0;
    ceilingY = targetCeilingY = 10;
  }
  ceilingY += (targetCeilingY - ceilingY) * 0.1;
  color("light_cyan");
  rect(0, 0, 100, ceilingY);
  color("light_blue");
  rect(0, 90, 100, 10);
  let scr = difficulty * 0.02;
  if (player.pos.x > 50) {
    scr += (player.pos.x - 50) * 0.1;
  }
  player.pos.x -= scr;
  if (player.bar != null) {
    const b = player.bar;
    player.pos.set(b.x, ceilingY).addWithAngle(b.angle, b.length);
    player.angleVel += b.angleVel * b.length * 0.003;
    if (b.x < 0) {
      color("red");
      text("X", 3, ceilingY);
      play("explosion");
      end();
    }
    if (input.isJustPressed) {
      play("select");
      player.vel
        .set()
        .addWithAngle(
          b.angle + PI / 2,
          (b.angleVel * b.length + player.angleVel * 3) * sqrt(difficulty)
        )
        .add(0, -sqrt(difficulty) * 0.5);
      player.bar = undefined;
      flyingTicks = 1;
    }
  } else {
    flyingTicks += difficulty;
    player.pos.add(player.vel);
    player.vel.y += (input.isPressed ? 0.01 : 0.1) * sqrt(difficulty);
    player.vel.mul(input.isPressed ? 0.99 : 0.95);
    if (player.pos.y > 89) {
      play("hit");
      player.vel.y *= -1.5;
      player.pos.y = 88;
      targetCeilingY += 10;
      flyingTicks = -9999;
      bars.forEach((b) => {
        b.isHeld = false;
      });
    }
    if (player.pos.x < 0) {
      color("red");
      text("X", 3, player.pos.y);
      play("explosion");
      end();
    }
  }
  player.angleVel *= 0.99;
  player.angle += player.angleVel;
  color("cyan");
  if (
    bar(player.pos, player.length, 4, player.angle, player.center).isColliding
      .rect.light_cyan &&
    player.vel.y < 0
  ) {
    player.vel.y *= -0.5;
  }
  nextBarDist -= scr;
  if (nextBarDist < 0) {
    const length = rnd(20, 50);
    bars.push({
      x: 110,
      length,
      angle: PI / 2 - rnd(PI / 4),
      angleVel: rnds(0.02, 0.04),
      isHeld: false,
    });
    nextBarDist = length + rnd(20);
  }
  remove(bars, (b) => {
    b.x -= scr;
    b.angleVel += cos(b.angle) * b.length * 0.00005 * sqrt(difficulty);
    b.angle += b.angleVel;
    color("black");
    const p = vec(b.x, ceilingY).addWithAngle(b.angle, b.length);
    line(b.x, ceilingY, p);
    color(b.isHeld ? "black" : "blue");
    const c = box(p, 5).isColliding.rect;
    if (c.light_blue) {
      play("explosion");
      color("red");
      text("X", p);
      end();
    }
    if (!b.isHeld && player.bar == null && c.cyan) {
      play("powerUp");
      if (flyingTicks > 0) {
        addScore(ceil(flyingTicks), p);
      }
      player.bar = b;
      b.isHeld = true;
      targetCeilingY = clamp(targetCeilingY - 5, 10, 99);
    }
    return b.x < -30;
  });
}


<|start_filename|>docs/lland/main.js<|end_filename|>
title = "LLAND";

description = `
[Hold] Thrust up
`;

characters = [
  `
 llll
l    l
 llll
 l  l
l ll l
ll  ll
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  theme: "pixel",
  seed: 6,
};

let mountains;
let shipY, shipV;
let offset;
let mountainAppDist;
let mountainIndex;
let landingIndex;
let landing;
let landY;
let shipCollision;
let m;
let isFirstLanded;

function update() {
  if (!ticks) {
    mountains = times(9, (i) => {
      if (i === 4) {
        return { y: (landY = 49), c: "cyan" };
      } else {
        return { y: 90 - i, c: "red" };
      }
    });
    shipY = 30;
    shipV = offset = mountainAppDist = mountainIndex = landing = isFirstLanded = 0;
    landingIndex = 7;
  }
  mountains.map((m, i) => {
    color(m.c);
    rect(wrap(i * 13 + offset - 13, -13, 104), m.y, 13, 99);
  });
  color("green");
  shipCollision = char("a", 25, shipY);
  if (landing) {
    if (input.isJustPressed) {
      landing = 0;
    } else {
      return;
    }
  }
  offset -= difficulty;
  if ((mountainAppDist -= difficulty) < 0) {
    m = mountains[wrap(mountainIndex, 0, 9)];
    m.y =
      landingIndex > 7 || landingIndex === 1
        ? rnd(70, 90)
        : landingIndex === 0
        ? (landY = rnd(40, 70))
        : rnd(40, 90);
    landingIndex--;
    if (landingIndex < 0) {
      m.c = "cyan";
      landingIndex = 9;
    } else {
      m.c = "red";
    }
    mountainIndex++;
    mountainAppDist += 13;
  }
  if (isFirstLanded) {
    if (input.isJustPressed) {
      play("laser");
      shipV -= 0.4;
    }
    if (input.isPressed) {
      shipV -= 0.2;
      particle(24.5, shipY + 2, 1, 1, PI / 2, 1);
    }
  }
  shipV += 0.1;
  shipV *= 0.99;
  if (shipY < 0 && shipV < 0) {
    shipV *= -1;
  }
  shipY += shipV * difficulty;
  if (shipCollision.isColliding.rect.cyan) {
    play("select");
    particle(24.5, shipY);
    landing = ++score;
    shipV = 0;
    shipY = landY - 3;
    mountains.map((n) => (n.c = "red"));
    isFirstLanded = 1;
  }
  if (shipCollision.isColliding.rect.red) {
    play("explosion");
    end();
  }
  if (rect(-1, 0, 1, 99).isColliding.rect.cyan) {
    color("red");
    for (let y = landY - 4; y < 99; y += 7) {
      text("X", 2, y);
    }
    play("explosion");
    end();
  }
}


<|start_filename|>docs/turbulent/main.js<|end_filename|>
title = "TURBULENT";

description = `
[Tap] Jump
`;

characters = [];

options = {
  theme: "dark",
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 300,
};

/** @type {{height: number, angle: number, va: number, x: number}[]} */
let waves;
/** @type {Vector[]} */
let points;
/** @type {{x: number, vx: number}[]} */
let mines;
let nextMineDist;
/**
 * @type {{pos: Vector, pp: Vector, vel: Vector, angle: number,
 * state: "float" | "jump"
 * }}
 */
let ship;
let jumpX;

function update() {
  if (!ticks) {
    waves = times(7, (i) => {
      return {
        height: rnd(10, 30),
        angle: (i % 2) * PI + rnds(PI / 4),
        va: rnd(0.01, 0.02),
        x: i * 20 - 20,
      };
    });
    points = times(25, (i) => vec());
    mines = [];
    nextMineDist = 0;
    ship = {
      pos: vec(40, 60),
      pp: vec(40, 60),
      vel: vec(),
      angle: 0,
      state: "float",
    };
  }
  let scr = 0.1 * difficulty;
  if (ship.pos.x > 50) {
    scr += (ship.pos.x - 50) * 0.1;
  }
  waves.forEach((w, i) => {
    w.x -= scr;
    if (w.x < -20) {
      w.x += 140;
      w.height = rnd(10, 30);
      w.angle = rnd(PI * 2);
      w.va = rnd(0.01, 0.02 * sqrt(difficulty));
    }
    w.angle += w.va;
    points[i * 4].set(w.x, 60 + sin(w.angle) * w.height);
  });
  color("blue");
  points.forEach((p, i) => {
    const im = i % 4;
    if (im !== 0) {
      let pp = points[floor(i / 4) * 4];
      let np = points[(floor(i / 4) + 1) * 4];
      const r = [0.2, 0.5, 0.8][im - 1];
      p.set(pp.x + 5 * im, pp.y * (1 - r) + np.y * r);
    }
    let pp = points[wrap(i - 1, 0, points.length)];
    if (pp.x < p.x) {
      line(pp, p);
    }
  });
  nextMineDist -= scr;
  if (nextMineDist < 0) {
    mines.push({ x: 103, vx: 0 });
    nextMineDist = rnd(100, 120) / sqrt(difficulty);
  }
  color("red");
  remove(mines, (m) => {
    m.x -= scr;
    const [pp, np] = getPoints(m.x);
    if (np == null) {
      return true;
    }
    const oy = np.y - pp.y;
    m.vx += oy * 0.001;
    m.vx *= 0.9;
    m.x += m.vx * sqrt(difficulty);
    const r = (m.x - pp.x) / (np.x - pp.x);
    text("*", m.x, pp.y + oy * r - 5);
    return m.x < -3;
  });
  let sa;
  if (ship.state === "float") {
    const [pp, np] = getPoints(ship.pos.x);
    if (np != null) {
      const oy = np.y - pp.y;
      ship.vel.x += oy * 0.002;
      ship.vel.x *= 0.925;
      ship.vel.x += 0.025;
      ship.pos.x += ship.vel.x;
      const r = (ship.pos.x - pp.x) / (np.x - pp.x);
      ship.pos.y = pp.y + oy * r;
      sa = pp.angleTo(np);
    }
    if (input.isJustPressed) {
      play("jump");
      jumpX = ship.pos.x;
      ship.vel.x = (ship.pos.x - ship.pp.x) * 2;
      ship.vel.y = (ship.pos.y - ship.pp.y) * 5;
      ship.vel.addWithAngle(sa - PI / 2, 1);
      if (ship.vel.y > -1) {
        ship.vel.y = -1;
      }
      ship.pos.add(ship.vel);
      ship.state = "jump";
    }
  } else {
    jumpX -= scr;
    ship.vel.x += 0.005;
    ship.vel.y += input.isPressed ? 0.02 : 0.1;
    ship.vel.mul(0.98);
    ship.pos.add(ship.vel);
    sa = ship.vel.angle;
  }
  ship.pos.x -= scr;
  ship.pos.clamp(5, 95, 5, 95);
  ship.pp.set(ship.pos);
  ship.angle += wrap(sa - ship.angle, -PI, PI) * 0.1;
  sa = ship.angle;
  const p = vec(ship.pos);
  p.addWithAngle(sa - PI * 0.5, 2);
  color("red");
  bar(p, 3, 2, sa);
  p.addWithAngle(sa - PI * 0.4, 2);
  color("black");
  bar(p, 4, 2, sa);
  p.addWithAngle(sa - PI * 0.6, 2);
  const c = bar(p, 1, 2, sa).isColliding;
  if (ship.state === "jump" && c.rect.blue) {
    const d = ship.pos.x - jumpX;
    play("hit");
    if (d > 0) {
      play("powerUp");
      addScore(ceil(sqrt(d * d)), ship.pos);
    }
    ship.state = "float";
    ship.vel.x *= 0.5;
  }
  if (c.text["*"]) {
    play("explosion");
    end();
  }

  function getPoints(x) {
    let pp;
    let np;
    for (let i = 0; i < points.length; i++) {
      pp = points[wrap(i - 1, 0, points.length)];
      np = points[i];
      if (pp.x > np.x) {
        continue;
      }
      if (pp.x <= x && x < np.x) {
        return [pp, np];
      }
    }
    return [undefined, undefined];
  }
}


<|start_filename|>docs/parking/main.js<|end_filename|>
title = "PARKING";

description = `
[Hold]
 Turn right
`;

characters = [];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  isDrawingScoreFront: true,
  seed: 3,
};

/** @type {{pos: Vector, color: Color}[]} */
let cars;
let carAngle;
let carCount;
let nextCarDist;
/** @type {{pos: Vector, angle: number, color: Color}[]} */
let parkedCars;
let nextParkedCarDist;
let gold;
let roadY;
let multiplier;

function update() {
  if (!ticks) {
    cars = [];
    carAngle = -PI / 2;
    nextCarDist = 0;
    carCount = 1;
    parkedCars = [];
    nextParkedCarDist = 0;
    gold = undefined;
    roadY = 0;
    multiplier = 1;
  }
  const carSpeed = difficulty;
  let scr = carSpeed * 1.05;
  if (cars.length > 0) {
    const cy = cars[0].pos.y;
    if (cy < 50) {
      scr += (50 - cy) * 0.1;
    }
  } else {
    nextCarDist--;
  }
  roadY -= scr;
  color("light_black");
  rect(0, 0, 11, 100);
  rect(89, 0, 11, 100);
  times(3, (x) => {
    const lx = 30 + x * 20;
    times(6, (y) => {
      const ly = y * 20 - (roadY % 20) - 10;
      box(lx, ly, 3, 10);
    });
  });
  color("light_yellow");
  rect(82, (-roadY % 93) + 6, 7, 90);
  rect(82, (-roadY % 93) - 88, 7, 90);
  color("white");
  box(93, (-roadY % 200) - 50, 7, 44);
  color("light_black");
  let ly = (-roadY % 200) - 69;
  ["P", "A", "R", "K", "I", "N", "G"].forEach((c) => {
    text(c, 92, ly);
    ly += 6;
  });
  if (gold == null) {
    gold = vec(rnd(40, 70), rnd(-50, -20));
  }
  color("yellow");
  box(gold, 8);
  color("white");
  text("$", gold);
  gold.y += scr;
  if (gold.y > 103) {
    if (multiplier > 1) {
      multiplier--;
    }
    gold = undefined;
  }
  nextParkedCarDist -= scr;
  if (nextParkedCarDist < 0) {
    play("laser");
    parkedCars.push({
      pos: vec(rnd(80, 84), -5),
      angle: rnd((-PI / 8) * 3, -PI / 8),
      // @ts-ignore
      color: ["red", "blue", "cyan", "purple"][rndi(4)],
    });
    nextParkedCarDist = rnd() < 0.7 ? rnd(8, 12) : rnd(100, 200);
  }
  remove(parkedCars, (c) => {
    c.pos.y += scr;
    drawCar(c.pos, c.angle, c.color);
    return c.pos.y > 105;
  });
  nextCarDist -= scr - carSpeed;
  if (cars.length < carCount && nextCarDist < 0) {
    play("hit");
    cars.push({
      pos: vec(rnd(20, 60), -5),
      // @ts-ignore
      color: ["red", "blue", "cyan", "purple"][rndi(4)],
    });
    nextCarDist = 15 * sqrt(difficulty);
  }
  carAngle += (input.isPressed ? 1 : -1) * sqrt(difficulty) * 0.1;
  carAngle = clamp(carAngle, -PI / 2, 0);
  remove(cars, (c) => {
    const ca = c.pos.y < 3 ? -PI / 2 : carAngle;
    c.pos.addWithAngle(ca, carSpeed);
    c.pos.y += scr;
    const cl = drawCar(c.pos, ca, c.color).rect;
    if (cl.red || cl.blue || cl.cyan || cl.purple) {
      play("explosion");
      color("light_red");
      text("X", c.pos);
      end();
    }
    if (cl.light_yellow) {
      play("powerUp");
      addScore(multiplier * 10, c.pos);
      parkedCars.push({ pos: c.pos, angle: carAngle, color: c.color });
      carCount += 1 / carCount;
      return true;
    }
    if (cl.yellow) {
      if (gold.y > -3) {
        play("coin");
        addScore(multiplier, gold);
        multiplier++;
      }
      gold = undefined;
    }
    if (c.pos.y > 103) {
      play("explosion");
      color("light_red");
      text("X", c.pos.x, 96);
      end();
    }
  });

  function drawCar(p, a, c) {
    const o = vec();
    color("black");
    o.set(p).addWithAngle(a + PI / 4, 3);
    box(o, 3);
    o.set(p).addWithAngle(a + (PI / 4) * 3, 3);
    box(o, 3);
    o.set(p).addWithAngle(a + (PI / 4) * 5, 3);
    box(o, 3);
    o.set(p).addWithAngle(a + (PI / 4) * 7, 3);
    box(o, 3);
    color(c);
    return bar(p, 4, 4, a).isColliding;
  }
}


<|start_filename|>docs/twolane/main.js<|end_filename|>
title = "TWO LANE";

description = `
[Tap]
 Change Lane
[Hold] 
 Accel
`;

characters = [
  `
ll  ll
llrrll
  rr
 rppr
llrrll
ll  ll
`,
  `
 yyyy
yyYYyy
yyYYyy
yyYYyy
yyYYyy
 yyyy
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 10,
};

/**
 * @type {{
 * y: number, type: "left" | "right" | "both", coin: "left" | "right"| "none",
 * index: number
 * }[]}
 */
let roads;
let speed;
let x;
let targetX;
let currentType;
let lastScore;
let multiplier;
let lastMultiplier;
let isOutOfCourse;

function update() {
  if (!ticks) {
    roads = times(12, (i) => {
      return {
        y: i * 10 - 10,
        type: "both",
        coin: "none",
        index: i,
      };
    });
    speed = 1;
    x = targetX = 30;
    currentType = "both";
    lastScore = 0;
    multiplier = lastMultiplier = 1;
    isOutOfCourse = false;
  }
  color("light_green");
  rect(0, 0, 100, 100);
  if (input.isJustPressed) {
    play("laser");
    targetX = targetX === 30 ? 70 : 30;
  }
  if (isOutOfCourse) {
    speed *= 1 - 0.1 * sqrt(difficulty);
    if (speed < 0.1) {
      play("lucky");
      end();
    }
  } else if (input.isPressed) {
    speed += difficulty * 0.02;
  } else {
    speed += (difficulty - speed) * 0.1;
  }
  const sl = clamp(difficulty * 3, 1, 9);
  if (speed > sl) {
    speed = sl;
  }
  x += (targetX - x) * (0.15 * sqrt(speed));
  color("black");
  char("a", x, 90);
  roads.forEach((r) => {
    const py = r.y;
    r.y = wrap(r.y + speed, -10, 110);
    if (r.y < py) {
      if (rnd() < 0.1) {
        r.type =
          currentType === "both" ? (rnd() < 0.5 ? "left" : "right") : "both";
        currentType = r.type;
      } else {
        r.type = currentType;
      }
      if (r.coin !== "none") {
        if (multiplier > 1) {
          multiplier--;
        }
      }
      if (rnd() < 0.2) {
        r.coin = r.type === "both" ? (rnd() < 0.5 ? "left" : "right") : r.type;
      } else {
        r.coin = "none";
      }
    }
    color("light_black");
    rect(r.type === "right" ? 50 : 10, r.y, r.type === "both" ? 80 : 40, 10);
    color("white");
    if (r.index % 2 === 0) {
      if (r.type === "left" || r.type === "both") {
        rect(11, r.y, 5, 10);
      }
      if (r.type === "right" || r.type === "both") {
        rect(84, r.y, 5, 10);
      }
    } else {
      rect(r.type === "right" ? 50 : 47, r.y, r.type === "both" ? 6 : 3, 10);
    }
    if (r.coin !== "none") {
      color("black");
      const x = r.coin === "left" ? 30 : 70;
      if (char("b", x, r.y + 5).isColliding.char.a) {
        play("coin");
        lastScore = floor(speed * speed + 1);
        lastMultiplier = multiplier;
        addScore(lastScore * multiplier);
        if (multiplier < 32) {
          multiplier++;
        }
        r.coin = "none";
      }
    }
  });
  color("black");
  const c = char("a", x, 90).isColliding.rect;
  if (!c.light_black && !c.white) {
    play("hit");
    isOutOfCourse = true;
    multiplier = 1;
  } else {
    isOutOfCourse = false;
  }
  if (lastScore > 0) {
    color("black");
    text(`+${lastScore}x${lastMultiplier}`, 3, 9);
  }
}


<|start_filename|>docs/ladderdrop/main.js<|end_filename|>
title = "LADDER DROP";

description = `
[Tap] Drop
`;

characters = [
  `
llllll
ll l l
ll l l
llllll
 l  l
 l  l
  `,
  `
llllll
ll l l
ll l l
llllll
ll  ll
  `,
  `
llllll
llllll
llllll
llllll
 l  l
 l  l
  `,
  `
llllll
llllll
llllll
llllll
ll  ll
  `,
  `
b    b
bbbbbb
b    b
b    b
bbbbbb
b    b
  `,
  `
LLLLLL
r rr r
r rr r

rr rr
rr rr
  `,
  `
b    b
bbbbbb
b    b
b    b
bbbbbb
b    b
  `,
  `
RRRRRR
rrrrrr
rrrrrr
rrrrrr
rrrrrr
rrrrrr
  `,
  `
 yyy
yYYYy
yYyYy
yYYYy
 yyy
  `,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 19,
};

/**
 * @type {{
 * pos: Vector, size: Vector, lxs: number[],
 * state: "wait" | "drop" | "fix", hasCoin: boolean
 * }[]}
 */
let panels;
let pvx;
let nextPanelX;
let coinPanelCount;
/**
 * @type {{
 * pos: Vector, vx: -1 | 1, state: "walk" | "up" | "down" | "downWalk" | "drop"
 * }}
 */
let player;
let lockCount;
/** @type {Vector[]} */
let coins;
let scr;
let totalScr;
let multiplier;
const coinPanelInterval = 4;

function update() {
  if (!ticks) {
    panels = [
      {
        pos: vec(2, 88),
        size: vec(16, 2),
        lxs: [],
        state: "fix",
        hasCoin: false,
      },
    ];
    pvx = 1;
    nextPanelX = 50;
    coinPanelCount = coinPanelInterval;
    addPanel();
    player = { pos: vec(9, 91), vx: 1, state: "walk" };
    coins = [];
    scr = totalScr = 0;
    multiplier = 1;
    lockCount = 0;
  }
  color("light_blue");
  rect(0, 0, 2, 100);
  rect(98, 0, 2, 100);
  color("black");
  let minY = 99;
  remove(panels, (p) => {
    if (p.state === "wait") {
      p.pos.x += pvx * sqrt(difficulty) * 1.5;
      nextPanelX = p.pos.x;
      if (p.pos.x < -9) {
        pvx *= -1;
        p.pos.x = -9;
      } else if (p.pos.x > 109 - p.size.x * 6) {
        pvx *= -1;
        p.pos.x = 109 - p.size.x * 6;
      }
      drawPanel(p);
      if (input.isJustPressed) {
        play("select");
        p.state = "drop";
      }
    } else if (p.state === "drop") {
      p.pos.y += 6 * sqrt(difficulty);
      color("transparent");
      let cl = drawPanel(p);
      if (cl.e || cl.f) {
        while (cl.e || cl.f) {
          p.pos.y--;
          cl = drawPanel(p);
        }
        p.pos.y = floor(p.pos.y) + (totalScr % 1);
        p.state = "fix";
        if (p.hasCoin) {
          p.hasCoin = false;
          times(p.size.x, (x) => {
            coins.push(vec(p.pos.x + x * 6 + 2, p.pos.y + 2));
          });
        }
        addPanel();
      }
      color("black");
      drawPanel(p);
    } else if (p.state === "fix") {
      p.pos.y += scr;
      color("black");
      drawPanel(p);
      if (p.pos.y < minY) {
        minY = p.pos.y;
      }
    }
    if (p.pos.y > 99) {
      if (p.state === "drop") {
        addPanel();
      }
      return true;
    }
  });
  color("black");
  player.pos.y += scr;
  if (player.state === "walk" || player.state === "downWalk") {
    player.pos.x += player.vx * sqrt(difficulty) * 0.4;
    let c = char(addWithCharCode("a", floor(ticks / 30) % 2), player.pos, {
      mirror: { x: player.vx },
    }).isColliding.char;
    if (c.h) {
      play("explosion");
      end();
    }
    if (c.f || player.pos.x < 5 || player.pos.x > 95) {
      play("laser");
      player.vx *= -1;
      player.pos.x += player.vx * 2;
      lockCount++;
      if (lockCount > 8) {
        player.pos.x = clamp(player.pos.x, 10, 90);
        player.pos.y -= 6;
        lockCount = 0;
        player.state = "drop";
      }
    } else {
      lockCount = 0;
    }
    if (c.e) {
      if (player.state === "walk") {
        player.state = "up";
      }
    } else {
      player.state = "walk";
      color("transparent");
      c = char("a", player.pos.x, player.pos.y + 6).isColliding.char;
      if (!(c.e || c.f)) {
        player.state = "drop";
      }
    }
  } else if (player.state === "up") {
    play("hit");
    player.pos.y -= sqrt(difficulty) * 0.3;
    color("transparent");
    let c = char("c", player.pos).isColliding.char;
    if (!c.e && c.f) {
      player.state = "down";
    } else if (!c.e) {
      let py = player.pos.y;
      while (c.e) {
        py++;
        c = char("c", player.pos.x, py).isColliding.char;
      }
      player.pos.y = floor(py) + (totalScr % 1);
      player.pos.x += player.vx * sqrt(difficulty) * 0.5;
      player.state = "walk";
    }
    color("black");
    char(addWithCharCode("c", floor(ticks / 30) % 2), player.pos);
  } else if (player.state === "down") {
    play("hit");
    player.pos.y += sqrt(difficulty) * 0.4;
    color("transparent");
    let c = char("c", player.pos.x, player.pos.y + 6).isColliding.char;
    if (!c.e && c.f) {
      let py = player.pos.y + 6;
      while (c.f) {
        py--;
        c = char("c", player.pos.x, py).isColliding.char;
      }
      player.pos.y = floor(py) + (totalScr % 1);
      player.state = "downWalk";
    }
    color("black");
    char(addWithCharCode("c", floor(ticks / 30) % 2), player.pos);
  } else {
    player.pos.y += sqrt(difficulty) * 0.5;
    color("transparent");
    let c = char("a", player.pos).isColliding.char;
    if (c.e || c.f) {
      let py = player.pos.y;
      while (!(c.e || c.f)) {
        py--;
        c = char("a", player.pos).isColliding.char;
      }
      player.pos.y = floor(py - 1) + (totalScr % 1);
      player.state = "walk";
    }
    color("black");
    char("a", player.pos, { mirror: { x: player.vx } });
  }
  if (player.pos.y > 99) {
    play("explosion");
    end();
  }
  color("black");
  remove(coins, (c) => {
    c.y += scr;
    const cl = char("i", c).isColliding.char;
    if (cl.a || cl.b) {
      play("coin");
      addScore(multiplier, c);
      multiplier++;
      return true;
    }
    if (c.y > 36 && (cl.e || cl.f)) {
      return true;
    }
    if (c.y > 103) {
      if (multiplier > 1) {
        multiplier--;
      }
      return true;
    }
  });
  scr = 0.01 * difficulty;
  if (minY < 30) {
    scr += (30 - minY) * 0.1;
  }
  totalScr += scr;

  function drawPanel(p) {
    const lc = p.state == "fix" ? "e" : "g";
    const fc = p.state == "drop" ? "h" : "f";
    let c;
    let li = 0;
    let cl = {};
    times(p.size.x, (x) => {
      times(p.size.y, (y) => {
        c = undefined;
        if (y === 0 && p.hasCoin) {
          c = "i";
        } else if (y >= 1 && li < p.lxs.length && x === p.lxs[li]) {
          c = lc;
        } else if (y === 1) {
          c = fc;
        }
        if (c != null) {
          const clc = char(c, p.pos.x + x * 6 + 3, p.pos.y + y * 6 + 3)
            .isColliding.char;
          cl = { ...cl, ...clc };
        }
      });
      if (x === p.lxs[li]) {
        li++;
      }
    });
    return cl;
  }

  function addPanel() {
    const size = vec(rndi(4, 8), rndi(3, 6));
    let lx = -1;
    let nd = rndi(1, 5);
    const lxs = [];
    while (lx < size.x) {
      lx += nd;
      lxs.push(lx);
      lx += rndi(2, 5);
    }
    let hasCoin = false;
    coinPanelCount--;
    if (coinPanelCount === 0) {
      coinPanelCount = coinPanelInterval;
      hasCoin = true;
    }
    panels.push({
      pos: vec(clamp(nextPanelX, 2, 98 - size.x * 6), 0),
      size,
      lxs,
      state: "wait",
      hasCoin,
    });
  }
}


<|start_filename|>docs/pairsdrop/main.js<|end_filename|>
title = "PAIRS DROP";

description = `
[Tap] Open
`;

characters = [
  `
l  l
l l l
l l l
l l l
l  l
`,
  `
l l l
 l l 
l l l
 l l 
l l l
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 101,
};

/** @type {{pos: Vector, n: number, isOpen: boolean, vy: number}[]} */
let cards;
/** @type {Vector} */
let nextCardPos;
let scr;
let openedCards;
let openedTicks;
let multiplier;

function update() {
  if (!ticks) {
    cards = [];
    nextCardPos = vec(20, -6);
    scr = 0;
    openedCards = [];
    openedTicks = 0;
    multiplier = 1;
  }
  let minY = 99;
  let maxY = 0;
  openedTicks--;
  if (openedTicks === 0) {
    remove(openedCards, (c) => {
      c.isOpen = false;
      return true;
    });
  }
  let isOpening = false;
  remove(cards, (c) => {
    const cl = c.isOpen ? (openedTicks > 0 ? "green" : "cyan") : "blue";
    color(cl);
    rect(c.pos.x - 4, c.pos.y - 5, 9, 11);
    color("white");
    rect(c.pos.x - 3, c.pos.y - 4, 7, 9);
    color(cl);
    c.pos.y += scr + c.vy;
    if (c.vy > 0) {
      c.vy += 0.1;
    }
    if (c.isOpen) {
      if (c.n === 0) {
        text("A", c.pos);
      } else if (c.n < 9) {
        text(`${c.n + 1}`, c.pos);
      } else if (c.n === 9) {
        char("a", c.pos);
      } else {
        text(`${["J", "Q", "K"][c.n - 10]}`, c.pos);
      }
    } else {
      char("b", c.pos);
      if (
        c.vy === 0 &&
        input.isJustPressed &&
        abs(c.pos.x - input.pos.x) < 5 &&
        abs(c.pos.y - input.pos.y) < 6
      ) {
        play("hit");
        if (openedTicks >= 0) {
          remove(openedCards, (c) => {
            c.isOpen = false;
            return true;
          });
          openedTicks = 0;
        }
        isOpening = true;
        c.isOpen = true;
        openedCards.push(c);
      }
    }
    if (c.pos.y < minY) {
      minY = c.pos.y;
    }
    if (c.pos.y > maxY) {
      maxY = c.pos.y;
    }
    if (c.vy === 0 && c.pos.y > 95) {
      play("explosion");
      color("red");
      text("X", c.pos);
      end();
    }
    if (c.pos.y > 99) {
      play("coin");
      addScore(multiplier, c.pos);
      multiplier++;
      return true;
    }
  });
  if (openedTicks < 0 && openedCards.length === 2) {
    if (openedCards[0].n === openedCards[1].n) {
      play("powerUp");
      fallCards(openedCards[0]);
      fallCards(openedCards[1]);
      openedCards = [];
      multiplier = 1;
    } else {
      play("laser");
    }
    openedTicks = 60;
  }
  nextCardPos.y += scr;
  scr += (difficulty * 0.0015 - scr) * 0.2 + (isOpening ? 0.15 : 0);
  if (maxY < 50) {
    scr += (50 - maxY) * 0.01;
  }
  if (minY > -6) {
    addCards();
  }

  function fallCards(rc) {
    let p = rc.pos;
    cards.forEach((c) => {
      if (c.pos.x === p.x && c.pos.y >= p.y) {
        c.vy = 1;
      }
    });
  }

  function addCards() {
    let ns = times(26, (i) => i % 13);
    times(99, () => {
      let i1 = rndi(26);
      let i2 = rndi(26);
      let tn = ns[i1];
      ns[i1] = ns[i2];
      ns[i2] = tn;
    });
    ns.forEach((n) => {
      cards.push({ pos: vec(nextCardPos), n, isOpen: false, vy: 0 });
      nextCardPos.x += 10;
      if (nextCardPos.x > 80) {
        nextCardPos.x = 20;
        nextCardPos.y -= 12;
      }
    });
  }
}


<|start_filename|>docs/pumppress/main.js<|end_filename|>
title = "PUMP PRESS";

description = `
[Tap]  Shot
[Hold] Speed up
`;

characters = [
  `
 lll
 lccl
rrrrrr
rrrrrr
 llll
 lll
`,
  `
    r
llllrr
    r
`,
  `
  ll
 ll ll
yyyyy
yyyyy
 ll ll
  ll
`,
];

options = {
  theme: "pixel",
  viewSize: { x: 200, y: 50 },
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 7,
};

/**
 * @type {{
 * x: number, vx: number, size: number, isPressed: boolean, pressedOfs: Vector
 * }[]}
 */
let enemies;
let nextEnemyDist;
let rocketX;
let isRocketGoing;
let nextRocketDist;
let shipX;
let shipSpeed;
let shotX;
let shotRange;
let pressedCount;
let scr;

function update() {
  if (!ticks) {
    enemies = [];
    nextEnemyDist = 0;
    rocketX = undefined;
    isRocketGoing = false;
    nextRocketDist = 30;
    shipX = 100;
    shipSpeed = 1;
    shotX = undefined;
    shotRange = 0;
    pressedCount = 0;
    scr = 0;
  }
  color("light_purple");
  rect(0, 20, 200, 1);
  rect(0, 29, 200, 1);
  color("black");
  shipX += difficulty * shipSpeed;
  shipSpeed += ((input.isPressed ? 1 : 0.5) - shipSpeed) * 0.1;
  if (input.isPressed) {
    particle(shipX, 25, 1, 2, PI, 0.3);
  }
  scr = difficulty * 0.1;
  if (shipX > 100) {
    scr += (shipX - 100) * 0.1;
  }
  shipX -= scr;
  char("a", shipX, 25);
  if (shotX == null && input.isJustPressed) {
    play("laser");
    shotX = shipX + 6;
    shotRange = 60;
  }
  if (shotX != null) {
    const s = difficulty * 3;
    shotX += s - scr;
    shotRange -= s;
    char("b", shotX, 25);
    if (shotRange < 0) {
      shotX = undefined;
    }
  }
  nextEnemyDist -= scr;
  if (nextEnemyDist < 0) {
    enemies.push({
      x: 205,
      vx: rnd(0.5, 0.4 + 0.1 * sqrt(difficulty)) * difficulty,
      size: 0,
      isPressed: false,
      pressedOfs: vec(rnds(2) - 4, rnds(2)),
    });
    nextEnemyDist = rnd(40 - sqrt(difficulty) * 10, 40) / sqrt(difficulty) + 3;
  }
  if (rocketX != null) {
    rocketX -= scr;
    if (isRocketGoing) {
      particle(rocketX, 25, 1, difficulty, 0, 0.4);
      rocketX -= difficulty;
    }
    const c = char("c", rocketX, 25).isColliding.char;
    if (!isRocketGoing && c.b) {
      isRocketGoing = true;
      shotX = undefined;
    }
    if (rocketX < 5) {
      if (pressedCount > 0) {
        play("explosion");
        addScore(((1 + pressedCount) * pressedCount) / 2, 20, 20);
        particle(rocketX, 25, pressedCount * 5, sqrt(pressedCount) * 0.5 + 2);
      }
      rocketX = undefined;
      pressedCount = 0;
    }
  } else {
    nextRocketDist -= scr;
    if (nextRocketDist < 0) {
      rocketX = 205;
      isRocketGoing = false;
      nextRocketDist = 100;
    }
  }
  remove(enemies, (e) => {
    let y = 24;
    if (!e.isPressed) {
      if (e.size > 0) {
        e.size -= difficulty * 0.02;
        if (e.size < 0) {
          e.size = 0;
        }
      } else {
        e.x += e.vx * (e.x < shipX ? 1 : -1);
      }
      e.x -= scr;
    } else {
      if (rocketX == null) {
        return true;
      }
      e.x = rocketX + e.pressedOfs.x;
      y += e.pressedOfs.y;
    }
    const s = ceil(e.size) * 2 + 2;
    color("red");
    rect(e.x - s, y - s, s * 2, 2);
    rect(e.x - s, y + s, s * 2, 2);
    rect(e.x - s, y - s, 2, s * 2);
    rect(e.x + s - 2, y - s, 2, s * 2);
    color("yellow");
    const c = rect(e.x - s, y, s * 2, 2).isColliding.char;
    if (!e.isPressed) {
      if (c.b) {
        shotX = undefined;
        e.size = ceil(e.size) + 1;
        if (e.size > 3) {
          play("explosion");
          color("red");
          particle(e.x, 25);
          return true;
        } else {
          play("hit");
        }
      }
      if (isRocketGoing && c.c) {
        e.isPressed = true;
        pressedCount++;
      }
      if (e.size === 0 && c.a) {
        play("powerUp");
        end();
      }
      return e.x < -5;
    }
  });
}


<|start_filename|>docs/bottop/main.js<|end_filename|>
title = "BOTTOP";

description = `
[Tap]  Jump
[Hold] Fly
`;

characters = [
  `
  ll
  l
 llll
l l
 l  l
l  l
`,
  `
  ll
  l
 lll
  l
  ll
 ll
`,
  `
  ll
l l  l
 llll
  l
 l lll
l   
`,
  `
ll  ll
 llll
llllll
llllll
  ll
  ll
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 2,
  theme: "pixel",
};

let y;
let vy;
let isJumping;
let spikes;
let spikeAddDist;
let scrolling;

function update() {
  if (!ticks) {
    y = vy = 0;
    isJumping = false;
    spikes = [];
    spikeAddDist = 0;
    scrolling = 1;
  }
  scrolling = difficulty;
  score += scrolling / 10;
  spikeAddDist -= scrolling;
  if (spikeAddDist < 0) {
    const y = rnd() < 0.33 ? (rnd() < 0.5 ? 8 : 92) : rnd(8, 92);
    spikes.push({ p: vec(103, y) });
    spikeAddDist += rnd(30, 60);
  }
  color("red");
  spikes = spikes.filter((s) => {
    s.p.x -= scrolling;
    char("d", s.p, { rotation: floor(ticks / 10) });
    return s.p.x > -3;
  });
  if (!isJumping && input.isPressed) {
    play("powerUp");
    isJumping = true;
    vy = 3;
  }
  if (isJumping) {
    vy -= input.isPressed ? 0.1 : 0.3;
    y += vy;
    if (y < 0) {
      y = 0;
      isJumping = false;
    }
  }
  color("black");
  const c = isJumping ? "c" : addWithCharCode("a", floor(ticks / 15) % 2);
  if (
    char(c, 9, 8 + y, { mirror: { y: -1 } }).isColliding.char.d ||
    char(c, 9, 92 - y).isColliding.char.d
  ) {
    play("explosion");
    end();
  }
  color("light_blue");
  rect(0, 0, 99, 5);
  rect(0, 95, 99, 5);
}


<|start_filename|>docs/bblast/main.js<|end_filename|>
title = "B BLAST";

description = `
[Hold] 
 Select area
[Release]
 Erase balls
`;

characters = [];

options = {
  theme: "shapeDark",
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 5,
};

/**
 * @type {{
 * pos: Vector, vel: Vector, radius: number, targetRadius: number, isRed:boolean
 * }[]}
 */
let balls;
let nextBallTicks;
let nextRedBallTicks;
let pos;
let radius;
let radiusSlowTicks;

function update() {
  if (!ticks) {
    balls = [];
    nextRedBallTicks = 0;
    nextBallTicks = 60;
    pos = vec();
    radius = undefined;
    radiusSlowTicks = 0;
  }
  nextRedBallTicks--;
  if (nextRedBallTicks < 0) {
    play("jump");
    balls.push({
      pos: vec(rnd(19, 80), 10),
      vel: vec(),
      radius: 0,
      targetRadius: 9,
      isRed: true,
    });
    nextRedBallTicks = 300 / difficulty;
  }
  nextBallTicks--;
  if (nextBallTicks < 0) {
    play("hit");
    balls.push({
      pos: vec(rnd(9, 90), rnd(20, 80)),
      vel: vec(),
      radius: 0,
      targetRadius: rnd(5, 10),
      isRed: false,
    });
    nextBallTicks = 30 / difficulty;
  }
  let hasRed = false;
  remove(balls, (b) => {
    b.radius += (b.targetRadius - b.radius) * 0.05;
    balls.forEach((ob) => {
      if (b === ob) {
        return;
      }
      const d = b.pos.distanceTo(ob.pos) - b.radius - ob.radius;
      if (d < 0) {
        b.vel.addWithAngle(ob.pos.angleTo(b.pos), -d / b.radius);
      }
    });
    if (
      (b.pos.x < b.radius && b.vel.x < 0) ||
      (b.pos.x > 99 - b.radius && b.vel.x > 0)
    ) {
      b.vel.x *= -0.7;
    }
    if (b.pos.y > 110 - b.radius && b.vel.y > 0) {
      b.vel.y *= -0.7;
    }
    b.vel.y += 0.05;
    b.vel.mul(0.95);
    if (b.vel.length > 1) {
      b.vel.mul(0.5);
    }
    b.pos.add(b.vel);
    if (b.isRed) {
      hasRed = true;
      if (b.pos.y > 99 - b.radius) {
        play("coin");
        addScore(10, b.pos);
        b.isRed = false;
      }
    }
    color(b.isRed ? "red" : "black");
    arc(b.pos, b.radius, b.radius * 0.4);
    if (b.pos.y - b.radius < 0) {
      play("lucky");
      color("red");
      text("X", b.pos.x, 7);
      end();
    }
  });
  if (!hasRed) {
    nextRedBallTicks = 0;
  }
  if (input.isJustPressed) {
    play("select");
    pos.set(input.pos).clamp(0, 99, 0, 99);
    radius = 1;
  }
  if (radius != null && input.isPressed) {
    radius += 0.5 * difficulty * (1 - radiusSlowTicks / 60);
    color("yellow");
    arc(pos, radius, 2);
  }
  if (radius != null && input.isJustReleased) {
    play("laser");
    radiusSlowTicks = 60;
    let multiplier = 1;
    const redPoss = [];
    remove(balls, (b) => {
      if (b.pos.distanceTo(pos) - b.radius - radius < 0) {
        if (b.isRed) {
          play("explosion");
          redPoss.push(b.pos);
          color("red");
          particle(b.pos, 30, 4);
        } else {
          play("coin");
          color("black");
          particle(b.pos, b.radius, b.radius * 0.3);
        }
        addScore(multiplier, b.pos);
        multiplier++;
        return true;
      }
    });
    redPoss.forEach((p) => {
      times(9, () => {
        balls.push({
          pos: vec(p.x + rnds(3), p.y + rnds(3)),
          vel: vec(),
          radius: 0,
          targetRadius: rnd(12, 15),
          isRed: false,
        });
      });
    });
  }
  if (radiusSlowTicks > 0) {
    radiusSlowTicks--;
  }
}


<|start_filename|>docs/cnodes/main.js<|end_filename|>
title = "C NODES";

description = `
[Tap] Cut
`;

characters = [];

options = {
  theme: "shape",
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 6,
};

/** @type {{pos: Vector, vel: Vector, color: "black" | "red" | "blue"}[]} */
let nodes;
let nextNodeDist;
/** @type {{node1: any, node2: any, length: number}[]} */
let lines;
let maxY;
let scr;
let pressedScr;
let multiplier = 1;
const fixedY = 20;

function update() {
  if (!ticks) {
    nodes = [];
    nextNodeDist = 0;
    lines = [];
    scr = 0;
    pressedScr = 0;
  }
  color("light_red");
  rect(0, 0, 100, fixedY);
  color("light_black");
  if (input.isJustPressed && input.pos.y > fixedY) {
    play("hit");
    box(input.pos, 7);
    multiplier = 1;
    pressedScr = 1;
  }
  scr = difficulty * 0.03 + pressedScr;
  pressedScr *= 0.7;
  if (maxY < fixedY + 9) {
    scr += (fixedY + 9 - maxY) * 0.1;
  }
  nextNodeDist -= scr;
  if (nextNodeDist < 0) {
    let nn = { pos: vec(rnd(10, 90), 0), vel: vec(), color: "black" };
    nodes.forEach((n) => {
      if (
        n.color === "black" &&
        abs(nn.pos.x - n.pos.x) / 2 + abs(nn.pos.y - n.pos.y) < 30 &&
        rnd() < 0.7
      ) {
        lines.push({
          node1: nn,
          node2: n,
          length: nn.pos.distanceTo(n.pos),
        });
      }
    });
    // @ts-ignore
    nodes.push(nn);
    play("laser");
    nextNodeDist = 4;
  }
  color("black");
  remove(lines, (l) => {
    const d = l.node1.pos.distanceTo(l.node2.pos) - l.length;
    const a = l.node1.pos.angleTo(l.node2.pos);
    l.node1.vel.addWithAngle(a, d * 0.02);
    l.node2.vel.addWithAngle(a, -d * 0.02);
    if (line(l.node1.pos, l.node2.pos, 2).isColliding.rect.light_black) {
      play("select");
      return true;
    }
    return l.node1.pos.y > 100 && l.node2.pos.y > 100;
  });
  maxY = 0;
  remove(nodes, (n) => {
    n.pos.y += scr;
    if (maxY < n.pos.y) {
      maxY = n.pos.y;
    }
    if (n.pos.y > fixedY) {
      if (n.color === "black") {
        n.color = "blue";
        checkRedNodes();
      }
      n.pos.add(n.vel);
      n.vel.y += 0.03;
      n.vel.mul(0.95);
    } else {
      n.vel.set();
    }
    color(n.color);
    box(n.pos, 5);
    if (n.pos.y > 100 && n.color === "red") {
      play("explosion");
      color("red");
      text("X", n.pos.x, 95);
      end();
    }
    if (n.pos.y > 140) {
      play("powerUp");
      addScore(multiplier, n.pos.x - 1, 99);
      if (multiplier < 64) {
        multiplier *= 2;
      }
      return true;
    }
  });
  if (input.isPressed) {
    checkRedNodes();
  }

  function checkRedNodes() {
    nodes.forEach((n) => {
      if (n.color === "red") {
        n.color = "blue";
      }
    });
    for (let i = 0; i < 9; i++) {
      let isAddingRed = false;
      lines.forEach((l) => {
        if (
          l.node1.color === "black" ||
          l.node1.color === "red" ||
          l.node2.color === "black" ||
          l.node2.color === "red"
        ) {
          if (l.node1.color === "blue") {
            l.node1.color = "red";
            isAddingRed = true;
          }
          if (l.node2.color === "blue") {
            l.node2.color = "red";
            isAddingRed = true;
          }
        }
      });
      if (!isAddingRed) {
        break;
      }
    }
  }
}


<|start_filename|>docs/snake1/main.js<|end_filename|>
title = "SNAKE 1";

description = `
[Tap] Turn
`;

characters = [];

options = {
  theme: "crt",
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 9,
};

/** @type { {pos: Vector, angle: number, rotation: number} } */
let head;
let headMoveTicks;
let isHeadGettingDollar;
let isHeadTurning;
/** @type { Vector[] } */
let bodies;
/** @type { Vector[] } */
let dollars;
let wallChars;
let edgeWallChars;
const angleOfs = [
  [1, 0],
  [0, 1],
  [-1, 0],
  [0, -1],
];
const headChar = [">", "v", "<", "^"];

function update() {
  if (!ticks) {
    color("green");
    head = { pos: vec(8, 8), angle: 0, rotation: 1 };
    headMoveTicks = 0;
    isHeadGettingDollar = false;
    isHeadTurning = false;
    bodies = times(4, (i) => vec(4 + i, 8));
    dollars = [vec(12, 8)];
    wallChars = times(16, () => "#").join("");
    edgeWallChars = `#${times(14, () => " ").join("")}#`;
  }
  text(wallChars, 3, 9);
  for (let y = 1; y <= 13; y++) {
    text(edgeWallChars, 3, 9 + y * 6);
  }
  text(wallChars, 3, 9 + 14 * 6);
  if (!isHeadTurning && input.isJustPressed) {
    play("select");
    isHeadTurning = true;
  }
  headMoveTicks--;
  if (headMoveTicks < 0) {
    play("laser");
    if (!isHeadGettingDollar) {
      bodies.shift();
    } else {
      isHeadGettingDollar = false;
    }
    bodies.push(vec(head.pos));
    if (isHeadTurning) {
      head.angle = wrap(head.angle + head.rotation, 0, 4);
      isHeadTurning = false;
    }
    const ao = angleOfs[head.angle];
    head.pos.add(ao[0], ao[1]);
    headMoveTicks = 20 / difficulty;
  }
  bodies.forEach((b) => {
    text("o", b.x * 6 + 3, b.y * 6 + 3);
  });
  const c = text(
    headChar[wrap(head.angle + head.rotation, 0, 4)],
    head.pos.x * 6 + 3,
    head.pos.y * 6 + 3
  ).isColliding.text;
  if (c.o || c["#"]) {
    play("explosion");
    color("white");
    rect(head.pos.x * 6, head.pos.y * 6, 6, 6);
    color("green");
    text("X", head.pos.x * 6 + 3, head.pos.y * 6 + 3);
    end();
  }
  let ig = false;
  dollars = dollars.filter((d) => {
    const c = text("$", d.x * 6 + 3, d.y * 6 + 3).isColliding.text;
    if (c.v || c[">"] || c["<"] || c["^"]) {
      ig = true;
      return false;
    }
    return true;
  });
  if (ig) {
    play("coin");
    addScore(1);
    isHeadGettingDollar = true;
    head.rotation *= -1;
    color("transparent");
    for (let i = 0; i < 2; i++) {
      for (let j = 0; j < 99; j++) {
        let x = rndi(2, 14);
        let y = rndi(3, 14);
        const c = text("$", x * 6 + 3, y * 6 + 3).isColliding.text;
        if (c.v || c[">"] || c["<"] || c["^"] || c.o) {
        } else {
          dollars.push(vec(x, y));
          break;
        }
      }
    }
    color("green");
  }
}


<|start_filename|>docs/subjump/main.js<|end_filename|>
title = "SUB JUMP";

description = `
[Hold]
 Go up & 
 Speed up
`;

characters = [
  `
   ll
   l
  lll
l l l
llllll
l lll
`,
  `
   ll
   l
  lll
  l l
llllll
  lll
`,
  `
 llll
ll lll
ll lll
ll lll
ll lll
 llll
`,
];

options = {
  theme: "crt",
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 4,
};

/** @type {Vector[]} */
let points;
/** @type {"sea" | "land"} */
let landForm;
let nextLandFormDist;
let lvy;
/** @type {{pos: Vector, vel: Vector}} */
let sub;
/** @type {Vector[]} */
let coins;
let nextCoinDist;
let multiplier;

function update() {
  if (!ticks) {
    points = times(12, (i) => vec(i * 10 - 10, rnd(75, 85)));
    landForm = "sea";
    nextLandFormDist = 50;
    lvy = 0;
    sub = { pos: vec(5, 60), vel: vec() };
    coins = [];
    nextCoinDist = 0;
    multiplier = 1;
  }
  const sd = sqrt(difficulty);
  let scr = 0;
  if (sub.pos.x > 10) {
    scr += (sub.pos.x - 10) * 0.2;
  }
  nextLandFormDist -= scr;
  let gp = points[points.length - 1];
  points.forEach((p, i) => {
    p.x -= scr;
    const pp = points[wrap(i - 1, 0, 12)];
    if (p.x < -10) {
      if (nextLandFormDist < 0) {
        landForm = landForm === "land" ? "sea" : "land";
        nextLandFormDist =
          rnd(200, 300) / (landForm === "land" ? 7 / sqrt(difficulty) : 1);
      }
      p.x += 120;
      lvy += rnds(sd) * 2;
      lvy *= 0.95;
      if (landForm === "sea") {
        if (pp.y < 55) {
          lvy += 5;
        } else if (pp.y < 65) {
          lvy += 3;
        } else if ((pp.y < 65 && lvy < 0) || (pp.y > 90 && lvy > 0)) {
          lvy *= -0.5;
        }
        p.y = pp.y + lvy;
        if (nextLandFormDist < 60) {
          lvy += 4;
        }
      } else {
        if (pp.y > 50) {
          lvy -= 5;
        } else if ((pp.y < 40 && lvy < 0) || (pp.y > 45 && lvy > 0)) {
          lvy *= -0.5;
        }
        p.y = pp.y + lvy / 3;
      }
      p.y = clamp(p.y, 35, 95) + rnds(5);
    }
    if (pp.x < p.x) {
      color(pp.y < 50 || p.y < 50 ? "green" : "purple");
      line(pp, p, 2);
    } else {
      gp = pp;
    }
  });
  color("blue");
  rect(0, 50, 100, 2);
  if (input.isJustPressed) {
    play("select");
  }
  if (input.isPressed) {
    if (sub.pos.y > 50) {
      sub.vel.y -= sd * 0.06;
      sub.vel.x += (1 * sd - sub.vel.x) * 0.1;
    } else {
      sub.vel.y += sd * 0.01;
      sub.vel.x += (1 * sd - sub.vel.x) * 0.1;
    }
  } else {
    if (sub.pos.y > 50) {
      sub.vel.y += sd * 0.03;
      sub.vel.x += (0.5 * sd - sub.vel.x) * 0.1;
    } else {
      sub.vel.y += sd * 0.05;
      sub.vel.x += (0.5 * sd - sub.vel.x) * 0.1;
    }
  }
  let py = sub.pos.y;
  sub.vel.mul(sub.pos.y > 50 ? 0.95 : 0.99);
  sub.pos.add(sub.vel);
  sub.pos.x -= scr;
  color("blue");
  if (py > 50 && sub.pos.y < 50) {
    play("jump");
    particle(sub.pos.x, 50, 9, 1, -PI / 2, PI);
  }
  if (py < 50 && sub.pos.y > 50) {
    play("hit");
    particle(sub.pos.x, 50, 9, 0.5, -PI / 2, PI);
  }
  color("black");
  const c = char(
    sub.pos.y < 50 ? "a" : addWithCharCode("a", floor(ticks / 7) % 2),
    sub.pos
  ).isColliding.rect;
  if (c.purple || c.green) {
    play("explosion");
    end();
  }
  if (sub.pos.y > 55) {
    color("blue");
    particle(sub.pos.x - 3, sub.pos.y + 1, 0.3, 1, PI, 0.1);
  }
  nextCoinDist -= scr;
  if (nextCoinDist < 0) {
    coins.push(vec(gp.x, gp.y - (gp.y < 50 ? rnd(5, 10) : rnd(10, 20))));
    nextCoinDist = rnd(10, 40);
  }
  color("yellow");
  remove(coins, (c) => {
    c.x -= scr;
    const cl = char("c", c).isColliding.char;
    if (cl.a || cl.b) {
      play("coin");
      addScore(multiplier, c);
      multiplier++;
      return true;
    }
    if (c.x < -3) {
      if (multiplier > 1) {
        multiplier--;
      }
      return true;
    }
  });
}


<|start_filename|>docs/lightdark/main.js<|end_filename|>
title = "LIGHT DARK";

description = `
[Tap] Jump
[Hold] Fly
`;

characters = [
  `
    ll
   l
  l
  ll
 l  l
l    l
`,
  `
  ll
  l
  l
  ll
  ll
 l  l
`,
  `
  ll
  ll
 llll
 llll
llllll
llllll
`,
];

options = {
  viewSize: { x: 200, y: 100 },
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 6,
};

/** @type {{x: number, type: "spike" | "coin", side: "light" | "dark" }[]} */
let objs;
let objDists;
let objTypes;
let pos;
let vel;
let state;
let side;
let multiplier;
let scx;

function update() {
  if (!ticks) {
    objs = [];
    for (let i = 0; i < 10; i++) {
      objs.push({
        x: i < 5 ? 25 + i * 9 : 99 + i * 9,
        type: "coin",
        side: i < 5 ? "light" : "dark",
      });
    }
    objDists = [200, 300];
    objTypes = ["coin", "spike"];
    pos = vec(9, 0);
    side = "light";
    vel = vec(2);
    state = "ground";
    multiplier = 1;
    scx = 0;
  }
  const scr = vel.x * difficulty;
  color("light_black");
  rect(0, 50, 200, 50);
  if (state === "ground") {
    if (input.isJustPressed) {
      side = side === "light" ? "dark" : "light";
      play(side === "light" ? "jump" : "laser");
      vel.y = 3 * sqrt(difficulty);
      pos.y = 7;
      state = "jump";
      scx = 0;
    }
  } else {
    if (input.isJustPressed) {
      play("hit");
      side = side === "light" ? "dark" : "light";
    }
    vel.y -= (input.isPressed ? 0.1 : 0.5) * difficulty;
    pos.y += vel.y;
    if (pos.y < 0) {
      pos.y = 0;
      state = "ground";
    }
  }
  const y = side === "light" ? 47 - pos.y : 53 + pos.y;
  color(side === "light" ? "black" : "white");
  const ch =
    state === "jump"
      ? "b"
      : addWithCharCode("a", floor((ticks * difficulty) / 10) % 2);
  const c = char(ch, pos.x, y, side === "light" ? {} : { mirror: { y: -1 } })
    .isColliding;
  objs = objs.filter((o) => {
    const y = o.side === "light" ? 46 : 54;
    color(o.side === "light" ? "black" : "white");
    let c;
    if (o.type === "spike") {
      c = char("c", o.x, y, o.side === "light" ? {} : { mirror: { y: -1 } })
        .isColliding;
    } else {
      c = text("o", o.x, y, o.side === "light" ? {} : { mirror: { y: -1 } })
        .isColliding;
    }
    if (c.char.a || c.char.b) {
      if (o.type === "spike") {
        play("explosion");
        end();
      } else {
        play(o.side === "light" ? "coin" : "select");
        addScore(multiplier, o.x + scx * 7, y);
        multiplier++;
        scx++;
        return false;
      }
    }
    o.x -= scr;
    if (o.x < -3) {
      if (o.type === "coin" && multiplier > 1) {
        multiplier--;
      }
      return false;
    }
    return true;
  });
  for (let i = 0; i < 2; i++) {
    objDists[i] -= scr;
    const side = i === 0 ? "light" : "dark";
    const o = objTypes[i] === "coin" ? 9 : 6;
    const c = objTypes[i] === "coin" ? rndi(4, 8) : rndi(5, 15);
    if (objDists[i] < 0) {
      for (let j = 0; j < c; j++) {
        objs.push({ x: 200 + j * o, type: objTypes[i], side });
      }
      objDists[i] = c * o + rnd(40, 120);
      objTypes[i] = objTypes[i] === "coin" ? "spike" : "coin";
    }
  }
}


<|start_filename|>docs/bamboo/main.js<|end_filename|>
title = "BAMBOO";

description = `
[Tap]  Turn
[Hold] Through
`;

characters = [
  `
  ll
  l  l
lpppp
  prrr
 r
r
`,
  `
   ll
   l  
lpppp
 rp  l
r  r
    r
`,
];

options = {
  viewSize: { x: 200, y: 100 },
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 1,
};

/** @type {{x: number, height: number, speed: number}[]} */
let bamboos;
let nextBambooTicks;
let x;
let vx;
let avx;
let animTicks;
let speedBambooTicks;

function update() {
  if (!ticks) {
    bamboos = [];
    nextBambooTicks = 0;
    x = 190;
    vx = 1;
    avx = 0;
    animTicks = 0;
    speedBambooTicks = 5;
  }
  color("black");
  if (input.isJustPressed) {
    play("select");
    vx *= -1;
  }
  x = wrap(x + vx * difficulty * (1 + avx), -3, 203);
  avx *= 0.9;
  animTicks += difficulty;
  char(
    input.isPressed ? "b" : addWithCharCode("a", floor(animTicks / 20) % 2),
    x,
    87,
    {
      mirror: { x: vx },
    }
  );
  nextBambooTicks--;
  if (nextBambooTicks < 0) {
    speedBambooTicks--;
    bamboos.push({
      x: rnd(5, 195),
      height: 0,
      speed: speedBambooTicks < 0 ? 2 : 1,
    });
    nextBambooTicks = rnd(70, 100) / difficulty;
    speedBambooTicks = rndi(4, 7);
  }
  remove(bamboos, (b) => {
    b.height += b.speed * difficulty * 0.14;
    let h = b.height / 4;
    let y = 90 - h / 2;
    if (h < 1) {
      y += (1 - h) * 3;
      h = 1;
    }
    let c = {};
    times(4, (i) => {
      color(
        b.height < 5
          ? "light_yellow"
          : b.height > 50
          ? "green"
          : b.height > 25
          ? i % 2 === 0
            ? "green"
            : "light_green"
          : b.height > 23
          ? "yellow"
          : i % 2 === 0
          ? "yellow"
          : "light_yellow"
      );
      c = { ...c, ...box(b.x, y, (4 - i) * 2, ceil(h)).isColliding.char };
      y -= h;
    });
    if ((c.a || c.b) && !input.isPressed) {
      if (b.height < 5) {
      } else if (b.height <= 25) {
        let s = ceil((b.height - 5) / 3);
        if (s === 7) {
          s = 10;
          play("powerUp");
        } else {
          play("coin");
        }
        addScore(s * s, b.x, 90 - b.height);
        return true;
      } else {
        play("hit");
        b.speed *= 0.6;
        b.height *= 0.7;
        avx++;
        if (avx > 5) {
          avx = 5;
        }
        vx *= -1;
        particle(
          b.x,
          87,
          9,
          difficulty * (1 + avx) * 0.5,
          vx > 0 ? 0 : PI,
          0.4
        );
        if (b.height <= 25) {
          play("explosion");
          return true;
        }
      }
    }
    if (b.height > 50) {
      b.speed *= 0.997;
    }
    if (b.height >= 89) {
      color("red");
      text("X", b.x, 3);
      play("lucky");
      end();
    }
  });
  color("purple");
  rect(0, 90, 200, 10);
}


<|start_filename|>docs/ref_collision/main.js<|end_filename|>
title = "";

description = `
`;

characters = [
  `
llllll
ll l l
ll l l
llllll
 l  l
 l  l
`
];

options = {
  isShowingScore: false
};

function update() {
  if (ticks === 0) {
  }
  const params = [50, 50, floor(input.pos.x - 50), floor(input.pos.y - 50)];
  color("cyan");
  box(50, 80, 15, 10);
  text("a", 80, 50);
  char("a", 70, 70);
  color("red");
  const cl = rect.apply(this, params);
  color("black");
  text(`rect(`, 5, 5);
  const l = params
    .map((p, i) => `${p}${i < params.length - 1 ? ", " : ""}`)
    .join("");
  text(`${l}`, 12, 12);
  text(").isColliding", 5, 19);
  color("blue");
  text("{", 5, 26);
  text(`rect:${JSON.stringify(cl.isColliding.rect)}`, 5, 33);
  text(`text:${JSON.stringify(cl.isColliding.text)}`, 5, 40);
  text(`char:${JSON.stringify(cl.isColliding.char)}`, 5, 47);
  text("}", 5, 54);
}


<|start_filename|>docs/hoppingp/main.js<|end_filename|>
title = "HOPPING P";

description = `
[Tap] Turn
`;

characters = [];

options = {
  viewSize: { x: 200, y: 100 },
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 1,
};

/**
 * @type {{
 * pos: Vector, vel:Vector, vx: number, hop:Vector, grv: number,
 * hopTicks: number, type: "player" | "enemy" | "power"}[]
 * }
 */
let hoppings;
let enemyAppTicks;
let powerAppTicks;
let powerTicks;
let multiplier;
let isFirstPower;
const baseHop = vec(0.5, -1.2);
const baseGrv = 0.02;
const texts = {
  player: "o",
  enemy: "x",
  power: "P",
};
/** @type { {[key:string]: Color} } */
const colors = {
  player: "green",
  enemy: "red",
  power: "yellow",
};
const enemyPattern = [
  [1, 1, 0.65],
  [1, 0.8, 2],
  [1.6, 0.6, 2],
  [1, 2, 1.8],
];

function update() {
  if (!ticks) {
    hoppings = [
      {
        pos: vec(99, 60),
        vel: vec(baseHop.x, 0),
        vx: 1,
        hop: baseHop,
        grv: baseGrv,
        hopTicks: 0,
        type: "player",
      },
    ];
    enemyAppTicks = 0;
    powerAppTicks = 200;
    powerTicks = 0;
    isFirstPower = true;
  }
  color("blue");
  rect(0, 90, 199, 9);
  const isWeakApp = powerTicks > 60;
  enemyAppTicks -= isWeakApp ? 3 : 1;
  if (enemyAppTicks < 0) {
    const x = rnd() < 0.5 ? -5 : 205;
    const t = enemyPattern[rndi(enemyPattern.length)];
    hoppings.push({
      pos: vec(x, 89),
      vel: vec(),
      vx: x < 99 ? 1 : -1,
      hop: vec(baseHop.x * t[0] * (isWeakApp ? 0.5 : 1), baseHop.y * t[1]),
      grv: baseGrv * t[2],
      hopTicks: 0,
      type: "enemy",
    });
    enemyAppTicks = (rnd(100, 150) / sqrt(difficulty)) * sqrt(hoppings.length);
  }
  if (hoppings.length === 1) {
    enemyAppTicks = 0;
  }
  powerAppTicks--;
  if (powerAppTicks < 0) {
    const x = rnd() < 0.5 ? -5 : 205;
    const t = isFirstPower
      ? [0.88, 1.25, 1.25]
      : enemyPattern[rndi(enemyPattern.length)];
    isFirstPower = false;
    hoppings.push({
      pos: vec(x, 89),
      vel: vec(),
      vx: x < 99 ? 1 : -1,
      hop: vec(baseHop.x * t[0] * 0.8, baseHop.y * t[1] * 0.8),
      grv: baseGrv * t[2] * 0.8,
      hopTicks: 0,
      type: "power",
    });
    powerAppTicks = rnd(700, 999) / sqrt(difficulty);
  }
  powerTicks--;
  if (powerTicks > 60) {
    if (powerTicks % 30 === 0) {
      play("select");
    }
  } else if (powerTicks > 0) {
    if (powerTicks % 10 === 0) {
      play("select");
    }
  }
  if (powerTicks <= 0) {
    multiplier = 1;
  }
  hoppings = hoppings.filter((h) => {
    if (h.type === "player" && input.isJustPressed) {
      play("hit");
      h.vx *= -1;
      h.vel.x *= -1;
    }
    if (h.hopTicks > 0) {
      h.hopTicks -= difficulty;
      if (h.hopTicks <= 0) {
        color("black");
        if (h.type === "player") {
          play("jump");
          particle(h.pos, 9, -h.hop.y, -PI / 2, PI / 2);
        } else {
          play("laser");
          particle(h.pos, 4, -h.hop.y, -PI / 2, PI / 2);
        }
        h.vel.set(h.hop.x * h.vx * difficulty, h.hop.y * difficulty);
      } else {
        const r = h.hopTicks < 5 ? 1 - h.hopTicks / 5 : (h.hopTicks - 5) / 5;
        color("black");
        box(h.pos.x, h.pos.y - 2 * r, 2, 4 * r);
        box(h.pos.x, h.pos.y - 5 * r, 6, 2 * r);
        const c = drawHopping(h, r);
        return checkCollision(h, c);
      }
    }
    h.pos.add(h.vel);
    h.vel.y += h.grv * difficulty * difficulty;
    if (h.type === "player" && powerTicks > 0) {
      h.pos.add(h.vel);
      h.vel.y += h.grv * difficulty * difficulty;
    }
    if (h.pos.y > 90) {
      h.pos.y = 90;
      h.hopTicks = 9;
    }
    if ((h.pos.x < 0 && h.vx < 0) || (h.pos.x > 199 && h.vx > 0)) {
      h.vx *= -1;
      h.vel.x *= -1;
    }
    color("black");
    box(h.pos.x, h.pos.y - 2, 2, 4);
    box(h.pos.x, h.pos.y - 5, 6, 2);
    const c = drawHopping(h, 1);
    return checkCollision(h, c);
  });

  function checkCollision(h, c) {
    if (h.type === "enemy") {
      if (c.o) {
        if (powerTicks > 0) {
          play("coin");
          color("blue");
          particle(h.pos, 19, 2);
          addScore(multiplier, h.pos);
          if (multiplier < 64) {
            multiplier *= 2;
          }
          return false;
        }
        play("explosion");
        end();
      }
    } else if (h.type === "power") {
      if (c.o) {
        play("powerUp");
        color("yellow");
        particle(h.pos, 29, 3);
        powerTicks = floor(
          (300 / sqrt(difficulty)) * sqrt(sqrt(hoppings.length))
        );
        hoppings.forEach((h) => {
          if (h.type !== "player") {
            h.vel.x *= -1;
            h.vx *= -1;
          }
        });
        return false;
      }
    }
    return true;
  }

  function drawHopping(h, r) {
    color(
      powerTicks > 0 &&
        h.type === "enemy" &&
        !(powerTicks < 60 && powerTicks % 15 > 7)
        ? "blue"
        : colors[h.type]
    );
    let c;
    if ((h.type === "player" && powerTicks > 0) || h.type === "power") {
      c = text(texts[h.type], h.pos.x, h.pos.y - 10 * r - 2, {
        scale: { x: 2, y: 2 },
      }).isColliding.text;
    } else {
      c = text(texts[h.type], h.pos.x, h.pos.y - 10 * r).isColliding.text;
    }
    return c;
  }
}


<|start_filename|>docs/flipbomb/main.js<|end_filename|>
title = "FLIPBOMB";

description = `
[Tap] Flip
`;

characters = [];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 2,
  theme: "crt",
};

let balls, bombs, explosions;
let bombAppTicks;
let ballAppTicks;
let multiplier;

function update() {
  if (!ticks) {
    balls = [];
    bombs = [];
    explosions = [];
    bombAppTicks = ballAppTicks = 0;
    multiplier = 1;
  }
  color("blue");
  line(99, 75, 75, 86);
  if (input.isJustPressed) {
    play("laser");
    line(70, 89, 50, 80);
    multiplier = 1;
  } else {
    line(70, 89, 50, 98);
  }
  color("purple");
  explosions = explosions.filter((e) => {
    let a = e.a;
    const r = e.t < 20 ? e.t * 0.5 : 20 * 0.5 - (e.t - 20);
    const s = e.t === 0 ? 10 : r + 3;
    times(5, (i) => {
      box(vec(e.p).addWithAngle(a, r), s, s);
      a += (PI * 2) / 5;
    });
    e.t++;
    e.a += 0.2;
    return e.t < 30;
  });
  ballAppTicks -= difficulty;
  if (ballAppTicks < 0) {
    balls.push({ p: vec(99, 70), v: vec(-1, 0.5).mul(difficulty), s: 0 });
    ballAppTicks = 60;
  }
  balls = balls.filter((b) => {
    b.p.add(b.v);
    color(b.s === 0 ? "blue" : "cyan");
    if (b.s === 0) {
      if (b.p.x < 70) {
        if (input.isJustPressed) {
          b.v.set(2, -4).rotate((b.p.x - 70) * 0.06);
          b.s = 1;
        }
      } else if (b.p.x < 50) {
        b.s = 1;
      }
    } else {
      particle(b.p, 1, b.v.length, b.v.angle + PI, 0.1);
    }
    if (box(b.p, 3, 3).isColliding.rect.purple) {
      return false;
    }
    return b.p.isInRect(0, 0, 99, 99);
  });
  bombAppTicks -= difficulty;
  if (bombAppTicks < 0) {
    const p = vec(rnd(80), 0);
    const v = vec(rnd(20, 70), 70)
      .sub(p)
      .div(500 / rnd(1, difficulty));
    bombs.push({ p, v });
    bombAppTicks += rnd(30, 50);
  }
  bombs = bombs.filter((b) => {
    b.p.add(b.v);
    color("red");
    const bc = box(b.p, 5, 5).isColliding.rect;
    if (bc.cyan || bc.purple) {
      play("hit");
      explosions.push({ p: b.p, t: 0, a: rnds(PI) });
      color("purple");
      particle(b.p, 16, 3);
      addScore(multiplier, b.p);
      multiplier++;
      return false;
    }
    if (b.p.y > 99 || bc.blue) {
      play("explosion");
      end();
    }
    return true;
  });
}


<|start_filename|>docs/zoneb/main.js<|end_filename|>
title = "ZONE B";

description = `
[Tap]  Turn
[Hold] Shot
`;

characters = [
  `
l
ll
ll
l
`,
  `
llll
`,
  `
lll
`,
  `
lll
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 15,
};

/**
 * @type {{
 * pos: Vector, angle: number, speed: number,
 * shotTicks: number, burstTicks: number, burstCount: number
 * turnTicks: number,
 * isReflecting: boolean
 * }[]}
 */
let enemies;
/** @type {{pos: Vector, width: number, angle: number}[]} */
let walls;
/** @type {{pos: Vector, angle: number, range: number, side: "player" | "enemy"}[]} */
let bullets;
/**
 * @type {{
 * pos: Vector, angle: number, speed: number,
 * isReflecting: boolean, shotTicks: number
 * }}
 */
let player;
let level;
let isLevelCleared;
let levelClearTicks;
/** @type {{pos:Vector, radius: number}} */
let nextCircleTarget;
/** @type {{pos:Vector, radius: number}} */
let nextCircle;
/** @type {{pos:Vector, radius: number}} */
let circle;
let circleTicks;
let multiplier;
const angleVels = [
  [1, 0],
  [0, 1],
  [-1, 0],
  [0, -1],
];
const shownRange = 30;

function update() {
  if (!ticks) {
    level = 0;
    isLevelCleared = true;
    levelClearTicks = 0;
  }
  if (isLevelCleared) {
    circleTicks = 99;
    circle = { pos: vec(50, 50), radius: 120 };
    nextCircle = { pos: vec(50, 50), radius: 60 };
    nextCircleTarget = { pos: vec(), radius: 0 };
    enemies = times(9 + level * 7, () => {
      return {
        pos: vec(rnd(99), rnd(99)),
        angle: rndi(4),
        speed: 0,
        shotTicks: rnd(200, 300),
        burstTicks: 0,
        burstCount: 0,
        turnTicks: 0,
        isReflecting: false,
      };
    });
    walls = times(19, () => {
      return {
        pos: vec(rnd(9, 89), rnd(9, 89)),
        width: rnd(5, 15),
        angle: rndi(2),
      };
    });
    bullets = [];
    player = {
      pos: vec(50, 80),
      angle: 3,
      speed: 0,
      isReflecting: false,
      shotTicks: 0,
    };
    level++;
    isLevelCleared = false;
    multiplier = 1;
  }
  circleTicks--;
  if (circleTicks === 9) {
    nextCircleTarget.radius = nextCircle.radius - 10;
    nextCircleTarget.pos.set(nextCircle.pos);
    for (let i = 0; i < 99; i++) {
      nextCircleTarget.pos.set(rnd(10, 90), rnd(10, 90));
      if (
        nextCircleTarget.pos.distanceTo(nextCircle.pos) <
        nextCircle.radius - nextCircleTarget.radius
      ) {
        break;
      }
    }
  }
  if (circleTicks < 0) {
    circleTicks = 600;
  }
  if (circleTicks < 9) {
    nextCircle.pos.add(
      (nextCircleTarget.pos.x - nextCircle.pos.x) / (circleTicks + 1),
      (nextCircleTarget.pos.y - nextCircle.pos.y) / (circleTicks + 1)
    );
    nextCircle.radius +=
      (nextCircleTarget.radius - nextCircle.radius) / (circleTicks + 1);
  }
  if (nextCircle.radius < 60) {
    color("light_black");
    arc(nextCircle.pos, nextCircle.radius, 2);
    if (circleTicks > 9) {
      circle.pos.add(
        (nextCircle.pos.x - circle.pos.x) / circleTicks,
        (nextCircle.pos.y - circle.pos.y) / circleTicks
      );
      circle.radius += (nextCircle.radius - circle.radius) / circleTicks;
    }
    color("blue");
    arc(circle.pos, circle.radius, 3);
  }
  color("yellow");
  remove(walls, (w) => {
    let c;
    if (w.angle === 0) {
      c = box(w.pos, w.width, 2);
    } else {
      c = box(w.pos, 2, w.width);
    }
    return c.isColliding.rect.blue;
  });
  remove(bullets, (b) => {
    const av = angleVels[b.angle];
    b.pos.add(av[0], av[1]);
    const isShown =
      b.side === "player" || b.pos.distanceTo(player.pos) < shownRange;
    color(isShown ? (b.side === "enemy" ? "purple" : "cyan") : "transparent");
    if (
      char(b.side === "enemy" ? "c" : "d", b.pos, { rotation: b.angle })
        .isColliding.rect.yellow ||
      !b.pos.isInRect(0, 0, 99, 99)
    ) {
      return true;
    }
    b.range--;
    return b.range < 0;
  });
  remove(enemies, (e) => {
    const av = angleVels[e.angle];
    e.pos.add(av[0] * e.speed, av[1] * e.speed);
    const isShown = e.pos.distanceTo(player.pos) < shownRange;
    if (isShown) {
      color("black");
      char("b", e.pos.x + av[0] * 2, e.pos.y + av[1] * 2, {
        rotation: e.angle,
      });
    }
    color(isShown ? "red" : "transparent");
    const c = char("a", e.pos, { rotation: e.angle }).isColliding;
    if (c.char.d) {
      play("explosion");
      addScore(multiplier, e.pos);
      multiplier++;
      return true;
    }
    if (c.rect.yellow || !e.pos.isInRect(0, 0, 99, 99)) {
      if (!e.isReflecting) {
        e.angle += 2;
        e.isReflecting = true;
      }
    } else {
      e.isReflecting = false;
    }
    if (e.shotTicks > 7) {
      e.turnTicks--;
    }
    if (e.turnTicks < 0) {
      e.angle++;
      e.turnTicks = rnd(200, 300);
    }
    e.angle = wrap(e.angle, 0, 4);
    e.shotTicks--;
    if (e.shotTicks < 0) {
      e.burstCount--;
      if (e.burstCount < 0) {
        e.shotTicks = rnd(100, 200);
        e.burstCount = rndi(3, 7);
      } else {
        bullets.push({
          pos: vec(e.pos.x + av[0] * 5, e.pos.y + av[1] * 5),
          angle: e.angle,
          range: 20,
          side: "enemy",
        });
        e.shotTicks += 7;
      }
    }
    e.speed += ((e.shotTicks < 7 ? 0 : 0.2) - e.speed) * 0.1;
    checkCircleReflect(e);
    e.pos.clamp(0, 99, 0, 99);
  });
  const av = angleVels[player.angle];
  player.speed += ((input.isPressed ? 0 : 0.2) - player.speed) * 0.1;
  if (input.isJustReleased) {
    if (player.speed > 0.04) {
      play("laser");
      player.angle = wrap(player.angle + 1, 0, 4);
    }
  }
  player.shotTicks--;
  if (input.isPressed && player.speed < 0.04) {
    if (player.shotTicks < 0) {
      play("hit");
      bullets.push({
        pos: vec(player.pos.x + av[0] * 5, player.pos.y + av[1] * 5),
        angle: player.angle,
        range: 20,
        side: "player",
      });
      player.shotTicks = 7;
    }
  }
  player.pos.add(av[0] * player.speed, av[1] * player.speed);
  checkCircleReflect(player);
  color("black");
  char("b", player.pos.x + av[0] * 2, player.pos.y + av[1] * 2, {
    rotation: player.angle,
  });
  color("blue");
  const c = char("a", player.pos, { rotation: player.angle }).isColliding;
  if (
    c.char.c ||
    circle.radius < 1 ||
    player.pos.distanceTo(circle.pos) > circle.radius * 1.05
  ) {
    play("lucky");
    end();
  }
  player.pos.clamp(0, 99, 0, 99);
  if (c.rect.yellow || !player.pos.isInRect(0, 0, 99, 99)) {
    if (!player.isReflecting) {
      player.angle += 2;
      player.isReflecting = true;
    }
  } else {
    player.isReflecting = false;
  }
  player.angle = wrap(player.angle, 0, 4);
  if (levelClearTicks <= -60 && enemies.length === 0) {
    play("powerUp");
    levelClearTicks = 60;
  }
  if (levelClearTicks > 0) {
    color("black");
    text("WINNER!", 30, 50);
    levelClearTicks--;
    if (levelClearTicks === 0) {
      isLevelCleared = true;
    }
  } else if (levelClearTicks > -60) {
    color("black");
    text(`LEVEL ${level}`, 30, 50);
    levelClearTicks--;
  }

  function checkCircleReflect(o) {
    if (o.pos.distanceTo(circle.pos) < circle.radius) {
      return;
    }
    const a = o.pos.angleTo(circle.pos);
    if (a < (-PI / 4) * 3 || a > (PI / 4) * 3) {
      o.angle = 2;
    } else if (a > PI / 4) {
      o.angle = 1;
    } else if (a < -PI / 4) {
      o.angle = 3;
    } else {
      o.angle = 0;
    }
  }
}


<|start_filename|>docs/upshot/main.js<|end_filename|>
title = "UP SHOT";

description = `
[Hold]
 Stop & Shoot
[Release]
 Run
`;

characters = [
  `
llllll
ll l l
ll l l
llllll
 l  l
 l  l
    `,
  `
llllll
ll l l
ll l l
llllll
ll  ll
    `,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 50,
};

/**
 * @type {{
 * x: number, size: number, speed: number,
 * interval: number, intervalVariation: number, ticks: number
 * }[]}
 */
let rockSpawns;
let nextRockSpawnDist;
/** @type {{pos: Vector, vy: number, size: number, speed: number}[]} */
let rocks;
/** @type {{pos: Vector, vx: number, shotTicks: number}} */
let player;
/** @type {{pos: Vector, vy: number}[]} */
let shots;
let lx;
let multiplier;

function update() {
  if (!ticks) {
    rockSpawns = [];
    nextRockSpawnDist = 0;
    rocks = [];
    player = { pos: vec(10, 87), vx: 0, shotTicks: 0 };
    shots = [];
    lx = 50;
    multiplier = 1;
  }
  let scr = difficulty * 0.1;
  if (player.pos.x > 30) {
    scr += (player.pos.x - 30) * 0.05;
  }
  lx = wrap(lx - scr, 0, 99);
  color("light_black");
  rect(0, 90, 100, 10);
  color("white");
  rect(lx, 90, 1, 10);
  player.shotTicks--;
  if (input.isPressed) {
    if (input.isJustPressed) {
      multiplier = 1;
      player.vx = 0;
    }
    if (player.shotTicks < 0) {
      play("laser");
      shots.push({ pos: vec(player.pos), vy: -2 * difficulty });
      player.shotTicks = 10 / difficulty;
    }
  } else if (input.isJustReleased) {
    play("select");
    player.vx = difficulty * 1.2;
  }
  player.pos.x += player.vx - scr;
  color("black");
  remove(shots, (s) => {
    s.pos.x -= scr;
    s.pos.y += s.vy;
    box(s.pos, 5, 9);
    return s.pos.y < 0;
  });
  nextRockSpawnDist -= scr;
  if (nextRockSpawnDist < 0) {
    const size = rnd(5, 15);
    let interval = rnd(10, 50) / difficulty;
    const speed = (rnd(5, 10) / sqrt(size)) * sqrt(difficulty);
    interval /= sqrt(speed) / sqrt(size);
    rockSpawns.push({
      x: 200,
      size,
      speed,
      interval,
      intervalVariation: rnd(0.3, 0.9),
      ticks: rnd(interval),
    });
    nextRockSpawnDist += rnd(50, 60);
  }
  remove(rockSpawns, (r) => {
    r.x -= scr;
    r.ticks--;
    if (r.ticks < 0) {
      rocks.push({
        pos: vec(r.x, -r.size / 2),
        vy: 0,
        size: r.size,
        speed: r.speed,
      });
      r.ticks = r.interval * (1 + rnds(r.intervalVariation));
    }
    return r.x < 0;
  });
  color("red");
  remove(rocks, (r) => {
    r.vy += r.speed * 0.01;
    r.pos.x -= scr;
    r.pos.y += r.vy;
    if (box(r.pos, r.size).isColliding.rect.black) {
      r.size *= 0.7;
      color("black");
      particle(r.pos, 5, 3, PI / 2, 0.5);
      color("red");
      if (r.size < 5) {
        play("powerUp");
        addScore(multiplier * 10, r.pos.x, clamp(r.pos.y, 20, 99));
        particle(r.pos, 19, 3);
        return true;
      } else {
        play("hit");
        addScore(multiplier, r.pos.x, clamp(r.pos.y, 20, 99));
        multiplier++;
      }
    }
    if (r.pos.y > 90 - r.size / 2) {
      particle(r.pos, r.size * 0.3, sqrt(r.size) * 0.3);
      return true;
    }
  });
  color("black");
  if (
    char(
      input.isPressed ? "b" : addWithCharCode("a", floor(ticks / 20) % 2),
      player.pos
    ).isColliding.rect.red ||
    player.pos.x < -2
  ) {
    play("explosion");
    end();
  }
  color("transparent");
  remove(shots, (s) => {
    return box(s.pos, 5, 9).isColliding.rect.red;
  });
}


<|start_filename|>docs/pinclimb/main.js<|end_filename|>
// Write the game name to 'title'.
title = "PIN CLIMB";

// 'description' is displayed on the title screen.
description = `
[Hold] Stretch
`;

// User-defined characters can be written here.
characters = [];

// Configure game options.
options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  // If you want to play a different BGM or SE,
  // you can try changing the 'seed' value.
  seed: 400,
};

// (Optional) Defining the types of variables is useful for
// code completion and error detection.
/** @type {{angle: number, length: number, pin: Vector}} */
let cord;
/** @type {Vector[]} */
let pins;
let nextPinDist;
const cordLength = 7;

// 'update()' is called every frame (60 times per second).
function update() {
  // 'ticks' counts the number of frames from the start of the game.
  if (!ticks) {
    // Initialize the game state here. (ticks === 0)
    pins = [vec(50, 0)]; // 'vec()' creates a 2d vector instance.
    nextPinDist = 10;
    cord = { angle: 0, length: cordLength, pin: pins[0] };
  }
  // 'difficulty' represents the difficulty of the game.
  // The value of this variable is 1 at the beginning of the game and
  // increases by 1 every minute.
  let scr = difficulty * 0.02;
  if (cord.pin.y < 80) {
    scr += (80 - cord.pin.y) * 0.1;
  }
  // 'input.isJustPressed' is set to true the moment the button is pressed.
  if (input.isJustPressed) {
    // 'play()' plays the SE.
    play("select");
  }
  // 'input.isPressed' is set to true while the button is pressed.
  if (input.isPressed) {
    cord.length += difficulty;
  } else {
    cord.length += (cordLength - cord.length) * 0.1;
  }
  cord.angle += difficulty * 0.05;
  // Draw a line connecting the coordinates of
  // the first argument and the second argument.
  line(cord.pin, vec(cord.pin).addWithAngle(cord.angle, cord.length));
  if (cord.pin.y > 98) {
    play("explosion");
    // Call 'end()' to end the game. (Game Over)
    end();
  }
  let nextPin;
  // 'remove()' passes the elements of the array of the first argument to
  // the function of the second argument in order and executes it.
  // If the function returns true, the element will be removed from the array.
  remove(pins, (p) => {
    p.y += scr;
    // Draw a box and check if it collides with other black rectangles or lines.
    if (box(p, 3).isColliding.rect.black && p !== cord.pin) {
      nextPin = p;
    }
    return p.y > 102;
  });
  if (nextPin != null) {
    play("powerUp");
    // Add up the score.
    // By specifying the coordinates as the second argument,
    // the added score is displayed on the screen.
    addScore(ceil(cord.pin.distanceTo(nextPin)), nextPin);
    cord.pin = nextPin;
    cord.length = cordLength;
  }
  nextPinDist -= scr;
  while (nextPinDist < 0) {
    // 'rnd()' returns a random value.
    pins.push(vec(rnd(10, 90), -2 - nextPinDist));
    nextPinDist += rnd(5, 15);
  }
}


<|start_filename|>docs/tapej/main.js<|end_filename|>
title = "<NAME>";

description = `
[Hold]    Pull
[Release] Release
`;

characters = [];

options = {
  viewSize: { x: 200, y: 100 },
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 3,
};

/** @type {{x: number, size: Vector}[]} */
let rects;
let nextRectDist;
/** @type {{from: Vector, to: Vector}[]} */
let tapes;
/**
 * @type {{
 * from: Vector, to: Vector, type: "ground" | "up" | "top" | "down", rect
 * }}
 */
let head;
/** @type {{pos: Vector, vel: Vector, size: number}[]} */
let fires;
let nextFireTicks;
let scr;
let dist;
const groundY = 90;

function update() {
  if (!ticks) {
    rects = [];
    nextRectDist = 0;
    tapes = [];
    head = {
      from: vec(100, groundY - 1),
      to: vec(100, groundY - 1),
      type: "ground",
      rect: undefined,
    };
    fires = [];
    nextFireTicks = 100;
    scr = 0;
    dist = 0;
  }
  if (input.isJustPressed) {
    play("select");
  }
  if (input.isPressed) {
    const spd = difficulty;
    dist += spd;
    switch (head.type) {
      case "ground":
        head.to.x += spd;
        break;
      case "up":
        head.to.y -= spd;
        if (head.to.y < groundY - head.rect.size.y) {
          head.to.y = groundY - head.rect.size.y;
          tapes.push({
            from: vec(head.from),
            to: vec(head.to),
          });
          head.type = "top";
          play("coin");
          head.from.set(head.to);
        }
        break;
      case "top":
        head.to.x += spd;
        if (head.to.x > head.rect.x + head.rect.size.x + 1) {
          head.to.x = head.rect.x + head.rect.size.x + 1;
          tapes.push({
            from: vec(head.from),
            to: vec(head.to),
          });
          head.type = "down";
          play("coin");
          head.from.set(head.to);
        }
        break;
      case "down":
        head.to.y += spd;
        if (head.to.y > groundY - 1) {
          head.to.y = groundY - 1;
          tapes.push({
            from: vec(head.from),
            to: vec(head.to),
          });
          head.type = "ground";
          play("coin");
          head.from.set(head.to);
          head.to.x += 3;
          head.rect = undefined;
        }
        break;
    }
  } else {
    if (dist > 0) {
      play("powerUp");
      addScore(floor(sqrt(dist) * dist * 0.1 + 1), head.to);
      dist = 0;
    }
    if (tapes.length > 0) {
      const t = tapes[0];
      t.from.x += (t.to.x - t.from.x) * 0.2 * sqrt(difficulty);
      t.from.y += (t.to.y - t.from.y) * 0.2 * sqrt(difficulty);
      if (t.from.distanceTo(t.to) < 3) {
        tapes.splice(0, 1);
      }
    } else {
      head.from.x += (head.to.x - head.from.x) * 0.2 * sqrt(difficulty);
      head.from.y += (head.to.y - head.from.y) * 0.2 * sqrt(difficulty);
    }
  }
  head.from.x -= scr;
  if (head.from.x < 0) {
    head.from.x = 0;
  }
  if (head.to.x < 0) {
    play("explosion");
    end();
  }
  head.to.x -= scr;
  color("black");
  remove(tapes, (t) => {
    t.from.x -= scr;
    if (t.from.x < 0) {
      t.from.x = 0;
    }
    t.to.x -= scr;
    line(t.from, t.to);
    return t.to.x < 0;
  });
  line(head.from, head.to, 3);
  color("light_yellow");
  let j = tapes.length > 0 ? tapes[0].from : head.from;
  box(j.x - 1, j.y - 1, 5, 5);
  color("purple");
  box(head.to, 3, 3);
  scr = difficulty * 0.1;
  if (head.to.x > 50) {
    scr += (head.to.x - 50) * 0.1;
  }
  nextRectDist -= scr;
  if (nextRectDist < 0) {
    const w = rnd(20, 40);
    rects.push({ x: 200, size: vec(w, rnd(10, 50)) });
    nextRectDist = w + rnd(20, 70);
  }
  color("light_black");
  rect(0, groundY, 200, 100 - groundY);
  remove(rects, (r) => {
    if (
      rect(r.x, 90, r.size.x, -r.size.y).isColliding.rect.purple &&
      head.type === "ground"
    ) {
      const to = vec(r.x - 1, groundY - 1);
      tapes.push({ from: vec(head.from), to });
      head.from.set(to);
      head.to.set(to);
      head.type = "up";
      play("coin");
      head.rect = r;
    }
    r.x -= scr;
    return r.x < -r.size.x;
  });
  nextFireTicks--;
  if (nextFireTicks < 0) {
    play("laser");
    const size = rnd(5, 15);
    const f = {
      pos: vec(rnd(150, 220), -size),
      vel: vec(-rnd(0.5, 1), rnd(0.8, 1.2)),
      size,
    };
    f.vel.mul(difficulty);
    fires.push(f);
    nextFireTicks = rnd(40, 60) / difficulty;
  }
  color("red");
  remove(fires, (f) => {
    f.pos.add(f.vel);
    f.pos.x -= scr;
    const cl = box(f.pos, f.size, f.size).isColliding.rect;
    particle(
      f.pos.x + rnds(f.size / 2),
      f.pos.y + rnds(f.size / 2),
      (rnd() * f.size) / 2,
      -f.vel.length,
      f.vel.angle,
      0.3
    );
    if (cl.black || cl.purple || cl.light_yellow) {
      play("explosion");
      end();
    }
    if (cl.light_black || f.pos.y > groundY) {
      play("hit");
      particle(f.pos, f.size * 5);
      return true;
    }
  });
}


<|start_filename|>docs/laserfortress/main.js<|end_filename|>
title = "LASER FORTRESS";

description = `
[Hold] Laser irradiation
`;

characters = [
  `
llllll
ll l l
ll l l
llllll
 l  l
 l  l
  `,
  `
llllll
ll l l
ll l l
llllll
ll  ll
  `,
  `
  lll
ll l l
 llll
 l  l
ll  ll
`,
  `
  lll
ll l l
 llll
  ll
 l  l
 l  l
`,
  `
  l
  l
ll ll
  l
  l
`,
  `
 l
l  l
lllll
l  l
 l
`,
];

options = {
  theme: "pixel",
  viewSize: { x: 160, y: 60 },
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 1,
};

/** @type {{x: number, vx: number, ticks: number, type: "ally" | "enemy"}[]} */
let objs;
let nextObjTicks;
let nextObjCount;
/** @type {"ally" | "enemy"} */
let nextObjType;
let highSpeedIndex;
let sightX;
let laserX;
let multiplier;

function update() {
  if (!ticks) {
    objs = [];
    nextObjTicks = 0;
    nextObjCount = 5;
    highSpeedIndex = -1;
    nextObjType = "enemy";
    sightX = 40;
    laserX = undefined;
    multiplier = 1;
  }
  color("blue");
  rect(0, 50, 200, 10);
  color("light_cyan");
  rect(0, 7, 14, 22);
  char("f", 17, 8);
  if (input.isJustPressed) {
    play("select");
    laserX = sightX;
    multiplier = 1;
    color("blue");
    particle(20, 8, 9, 3, 0, PI / 8);
  }
  if (laserX != null && input.isPressed) {
    play("laser");
    color("blue");
    line(laserX, 50, 20, 8, 2);
    laserX += difficulty * 2;
    particle(laserX, 50, 1, 2, -PI / 2, PI / 6);
    color("purple");
    box(laserX - 2, 50, 5, 1);
  }
  nextObjTicks--;
  if (nextObjTicks < 0) {
    objs.push({
      x: 163,
      vx: difficulty * (nextObjCount === highSpeedIndex ? 2 : 1),
      ticks: rndi(99),
      type: nextObjType,
    });
    nextObjCount--;
    if (nextObjCount < 0) {
      nextObjCount = 9 - floor(sqrt(rnd(50)));
      nextObjTicks = rnd(20, 30) / difficulty;
      if (nextObjType === "ally") {
        nextObjType = "enemy";
      } else if (rnd() < 0.3) {
        nextObjType = "ally";
      }
      highSpeedIndex = rnd() < 0.5 ? -1 : rndi(2);
    } else {
      nextObjTicks = rnd(5, 8) / difficulty;
    }
  }
  let minX = 200;
  remove(objs, (o) => {
    o.x -= o.vx;
    if (o.type === "enemy" && o.x < minX) {
      minX = o.x;
    }
    o.ticks++;
    color(o.type === "ally" ? "blue" : "red");
    if (
      char(
        addWithCharCode(o.type === "ally" ? "a" : "c", floor(o.ticks / 12) % 2),
        o.x,
        47,
        { mirror: { x: -1 } }
      ).isColliding.rect.purple
    ) {
      if (o.type === "ally") {
        play("explosion");
        end();
      } else {
        play("hit");
        particle(o.x, 47);
        addScore(multiplier, o.x, 47);
        multiplier++;
      }
      return true;
    }
    if (o.x < 0) {
      if (o.type === "enemy") {
        play("explosion");
        color("red");
        text("X", 3, 47);
        end();
      }
      return true;
    }
  });
  if (minX < 200) {
    sightX += (minX - difficulty * 3 - 5 - sightX) * 0.3;
  }
  color("black");
  char("e", sightX, 47);
}


<|start_filename|>docs/chargebeam/main.js<|end_filename|>
title = "CHARGE BEAM";

description = `
[Tap]     Shot
[Hold]    Charge
[Release] Fire
`;

characters = [
  `
rllbb
lllccb
llyl b
`,
  `
  r rr
rrrrrr
  grr
  grr
rrrrrr
  r rr
`,
  `
 LLLL
LyyyyL
LyyyyL
LyyyyL
LyyyyL
 LLLL
`,
  `
   bbb
  bccb
bbllcb
bcllcb
  bccb
   bbb
`,
  `
l llll
l llll
`,
];

options = {
  viewSize: { x: 200, y: 60 },
  theme: "pixel",
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 1,
};

/** @type {{x: number, size: number, type: "enemy" | "coin"}[]} */
let objs;
let nextObjDist;
let inhalingCoins;
let coinMultiplier;
let coinPenaltyMultiplier;
let enemyMultiplier;
let shotX;
let shotSize;
let charge;
let penaltyVx;
let prevType;

function update() {
  if (!ticks) {
    objs = [{ x: 150, size: 1, type: "enemy" }];
    for (let i = 0; i < 3; i++) {
      objs.push({ x: 160 + i * 10, size: 1, type: "coin" });
    }
    nextObjDist = 30;
    inhalingCoins = [];
    coinMultiplier = enemyMultiplier = coinPenaltyMultiplier = 1;
    shotX = shotSize = undefined;
    charge = 0;
    penaltyVx = 0;
    prevType = "coin";
  }
  if (shotX == null) {
    text("BEAM", 30, 55);
    if (input.isPressed && charge < 99) {
      play("hit");
      charge += difficulty * 1.5;
      color("cyan");
      let c = charge;
      let x = 60;
      if (c < 25) {
        rect(x, 53, c, 5);
        shotSize = 1;
      } else {
        rect(x, 53, 25, 5);
        c -= 25;
        x += 27;
        shotSize = 1;
        while (c > 9) {
          rect(x, 53, 9, 5);
          x += 11;
          c -= 9;
          shotSize++;
        }
        rect(x, 53, c, 5);
        shotSize++;
      }
      color("black");
    } else if (charge > 0) {
      play("laser");
      shotX = 10;
      charge = 0;
      coinMultiplier = enemyMultiplier = coinPenaltyMultiplier = 1;
    }
  }
  if (shotX != null) {
    shotX += difficulty * 2.5;
    let x = shotX;
    if (shotSize === 1) {
      char("e", shotX, 30);
    } else {
      for (let i = 0; i < shotSize; i++) {
        if (shotSize % 2 === 1 && i === 0) {
          char("d", x, 30);
          x += 6;
        } else {
          if (i % 2 === shotSize % 2) {
            char("d", x, 27);
          } else {
            char("d", x, 33);
            x += 6;
          }
        }
      }
    }
    if (shotX > 203) {
      shotX = undefined;
    }
  }
  penaltyVx -= 0.02;
  if (penaltyVx < 0) {
    penaltyVx = 0;
  }
  const vx = (-difficulty - penaltyVx) * 0.5;
  color("red");
  for (let i = 0; i < ceil(penaltyVx * 2 + 0.1); i++) {
    text("<", i * 6 + 3, 48);
  }
  color("black");
  nextObjDist += vx;
  if (nextObjDist < 0) {
    const type = prevType !== "coin" && rnd() < 0.5 ? "coin" : "enemy";
    prevType = type;
    const c = rndi(3, 9);
    let x = 200;
    if (type === "coin") {
      for (let i = 0; i < c; i++) {
        objs.push({ x, size: 1, type: "coin" });
        x += 10;
      }
    } else {
      for (let i = 0; i < c; i++) {
        const size = rnd() < 0.3 ? rndi(2, 6) : 1;
        objs.push({
          x,
          size,
          type: "enemy",
        });
        x += 10 + ceil((size - 1) / 2) * 6;
      }
    }
    x += 10;
    nextObjDist = x - 200;
  }
  let minCoinX = 999;
  let minEnemyX = 999;
  objs = objs.filter((o) => {
    o.x += vx;
    if (o.type === "coin") {
      if (o.x < minCoinX) {
        minCoinX = o.x;
      }
    } else {
      if (o.x < minEnemyX) {
        minEnemyX = o.x;
      }
    }
    if (o.type === "coin") {
      const c = char("c", o.x, 30).isColliding.char;
      if (c.d || c.e) {
        addCoinPenalty(o, c);
        return false;
      }
      if (c.b || c.c) {
        return false;
      }
    } else {
      let x = o.x;
      let c = {};
      for (let i = 0; i < o.size; i++) {
        if (o.size % 2 === 1 && i === 0) {
          c = { ...c, ...char("b", x, 30).isColliding.char };
          x += 6;
        } else {
          if (i % 2 === o.size % 2) {
            c = { ...c, ...char("b", x, 27).isColliding.char };
          } else {
            c = { ...c, ...char("b", x, 33).isColliding.char };
            x += 6;
          }
        }
      }
      if (c.d || c.e) {
        play("explosion");
        if (c.e) {
          shotX = undefined;
        }
        if (o.size <= shotSize) {
          particle(o.x, 30, o.size * 3);
          addScore(enemyMultiplier * o.size, o.x, 30);
          enemyMultiplier++;
          if (o.size > 1) {
            shotSize -= o.size;
            if (shotSize <= 0) {
              shotX = undefined;
            }
          }
          return false;
        } else {
          o.size -= shotSize;
          shotX = undefined;
        }
      }
      if (c.b || c.c) {
        return false;
      }
    }
    return true;
  });
  if (minCoinX > 200 && minEnemyX > 200) {
    nextObjDist = 0;
  }
  if (minCoinX < minEnemyX) {
    objs = objs.filter((o) => {
      if (o.type === "coin" && o.x < minEnemyX) {
        inhalingCoins.push({ x: o.x, vx: -1 });
        return false;
      }
      return true;
    });
  }
  if (char("a", 10, 30).isColliding.char.b) {
    play("lucky");
    end();
  }
  inhalingCoins = inhalingCoins.filter((o) => {
    o.x += o.vx;
    o.vx -= 0.1;
    const c = char("c", o.x, 30).isColliding.char;
    if (c.d || c.e) {
      addCoinPenalty(o, c);
      return false;
    }
    if (o.x < 10) {
      play("coin");
      addScore(coinMultiplier, 10 + sqrt(coinMultiplier) * 4, 30);
      if (coinMultiplier < 64) {
        coinMultiplier *= 2;
      }
      return false;
    }
    return true;
  });

  function addCoinPenalty(o, c) {
    play("powerUp");
    particle(o.x, 30, 9, 5);
    if (c.e) {
      shotX = undefined;
    }
    addScore(-coinPenaltyMultiplier, o.x + sqrt(coinPenaltyMultiplier) * 4, 50);
    if (coinPenaltyMultiplier < 64) {
      coinPenaltyMultiplier *= 2;
    }
    penaltyVx += 0.5;
  }
}


<|start_filename|>docs/bwalls/main.js<|end_filename|>
title = "B WALLS";

description = `
[Tap] Shoot
`;

characters = [
  `
  ll
  ll
rrLLrr
rrLLrr
  ll
`,
  `
l ll l
 lyyl
lyyyyl
 ylly
 ylly
  ll
`,
  `
 y
yyy
lll
yyy
lll
 y
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 19,
};

/**
 * @type {{
 * y: number, width: number, interval: number, ox: number, vx: number
 * }[]}
 */
let walls;
let nextWallDist;
let scr;
let wallTicks;
let shotY;
let multiplier;

function update() {
  if (!ticks) {
    walls = [];
    nextWallDist = 0;
    scr = 0;
    wallTicks = 0;
    shotY = undefined;
    multiplier = 1;
  }
  const wallStopInterval = 120 / sqrt(difficulty);
  const isWallStopping = (wallTicks / wallStopInterval) % 1 > 0.5;
  if (shotY == null && input.isJustPressed) {
    play("powerUp");
    shotY = 90;
    multiplier = 1;
  }
  if (shotY != null) {
    shotY -= sqrt(difficulty) * 3;
    char("c", 50, shotY);
    if (shotY > 66) {
      scr += (shotY - 66) * 0.1;
    }
  }
  if (!isWallStopping) {
    play("hit");
  }
  let maxY = 0;
  remove(walls, (w) => {
    w.y += scr;
    if (w.y > maxY) {
      maxY = w.y;
    }
    if (!isWallStopping) {
      w.ox = (w.ox + w.vx) % (w.width + w.interval);
    }
    let x = w.ox - w.width;
    color("yellow");
    while (x < 99) {
      if (rect(x, w.y, w.width, 5).isColliding.char.c) {
        play("select");
        shotY = undefined;
      }
      x += w.width + w.interval;
    }
    color("black");
    if (char("b", 50, w.y - 3).isColliding.char.c && shotY != null) {
      play("explosion");
      addScore(multiplier, 50, w.y - 3);
      multiplier *= 2;
      return true;
    }
  });
  if (shotY == null) {
    wallTicks++;
  } else if (shotY < -9 || (!isWallStopping && shotY < maxY + 7)) {
    play("select");
    shotY = undefined;
  }
  nextWallDist -= scr;
  if (nextWallDist < 0) {
    const w = rnd(10, 20);
    const i = rnd(20, 40);
    const wall = {
      y: -9 - nextWallDist,
      width: w,
      interval: i,
      ox: rnd(w + i),
      vx: 0,
    };
    let isValid = false;
    for (let i = 0; i < 99; i++) {
      const vx = rnds(5, 10) * sqrt(difficulty);
      if (abs((vx * wallStopInterval * 0.5) % (w + i)) > 19) {
        wall.vx = vx;
        isValid = true;
        break;
      }
    }
    if (!isValid) {
      wall.width = 0;
    }
    walls.push(wall);
    nextWallDist += 11;
  }
  if (maxY < 40) {
    scr += (40 - maxY) * 0.01;
  } else {
    scr *= 0.5;
  }
  scr += difficulty * 0.01;
  color("black");
  if (char("a", 50, 90).isColliding.rect.yellow) {
    play("lucky");
    end();
  }
}


<|start_filename|>docs/scaffold/main.js<|end_filename|>
title = "SCAFFOLD";

description = `
[Tap]
 Change angle
[Hold]
 Scaffold 
`;

characters = [
  `
    ll
   lll
  ll
 ll
ll
l
`,
  `




llllll
llllll
`,
  `
l
ll
 ll
  ll
   lll
    ll
`,
  `
llllll
ll l l
ll l l
llllll
 l  l
 l  l
  `,
  `
llllll
ll l l
ll l l
llllll
ll  ll
  `,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 100,
};

/** @type {{pos: Vector, type: number}[]} */
let floors;
/** @type {Vector} */
let nextFloorPos;
let nextFloorType;
let tv;
let pressedCount;
/** @type {Vector} */
let wall;
/**
 * @type {{
 * pos: Vector, vy: number, d: number, distance: number, type: "spike" | "gold"
 * }[]}
 */
let objs;
let nextObjDist;
/** @type {Vector} */
let player;
let multiplier;

function update() {
  if (!ticks) {
    floors = times(9, (i) => {
      return { pos: vec(i * 6 + 3, 52), type: 1 };
    });
    nextFloorPos = vec(9 * 6 + 3, 52);
    nextFloorType = 0;
    tv = 1;
    pressedCount = 0;
    wall = vec(-9, 0);
    objs = [];
    nextObjDist = 0;
    player = vec(5, 50);
    multiplier = 1;
  }
  let scr = difficulty * 0.05;
  if (nextFloorPos.x > 40) {
    scr += (nextFloorPos.x - 40) * 0.1;
  }
  wall.x -= scr;
  color("red");
  if (wall.x < -6) {
    let wy = 47;
    for (let i = 0; i < 9; i++) {
      const y = 47 + rndi(-9, 9) * 4;
      if (abs(y - nextFloorPos.y) < 25) {
        wy = y;
        break;
      }
    }
    wall.set(6 - (nextFloorPos.x % 6) + 120, wy);
    objs.push({
      pos: vec(wall.x + 4, wall.y + 4),
      vy: 0.1,
      d: 2,
      distance: 4,
      type: "gold",
    });
    color("purple");
    rect(100, 0, 40, 100);
    color("red");
    nextObjDist += 30;
  }
  rect(wall.x + 3, 0, 2, wall.y - 12);
  rect(wall.x + 3, wall.y + 18, 2, 100 - 18 - wall.y);
  rect(0, -7, 100, 9);
  rect(0, 98, 100, 9);
  if (input.isJustReleased) {
    play("laser");
    if (
      (nextFloorType === 0 && tv === -1) ||
      (nextFloorType === 2 && tv === 1)
    ) {
      tv *= -1;
    }
    nextFloorType += tv;
  }
  if (input.isPressed) {
    pressedCount++;
    if (pressedCount > 15 / sqrt(difficulty)) {
      play("select");
      floors.push({
        pos: vec(
          nextFloorPos.x,
          nextFloorPos.y + (nextFloorType === 2 ? 4 : 0)
        ),
        type: nextFloorType,
      });
      nextFloorPos.add(6, nextFloorType * 4 - 4);
      pressedCount = 0;
    }
  } else {
    pressedCount = 0;
  }
  color("black");
  remove(floors, (f) => {
    f.pos.x -= scr;
    char(addWithCharCode("a", f.type), f.pos);
    return f.pos.x < -3;
  });
  color("cyan");
  nextFloorPos.x -= scr;
  char(
    addWithCharCode("a", nextFloorType),
    nextFloorPos.x,
    nextFloorPos.y + (nextFloorType === 2 ? 4 : 0)
  );
  let vx = 0;
  if (player.x < 20) {
    vx += (20 - player.x) * 0.2;
  }
  player.x += vx - scr;
  if (
    char(addWithCharCode("d", floor(ticks / 15) % 2), player).isColliding.rect
      .red
  ) {
    play("explosion");
    end();
  }
  color("transparent");
  let type;
  for (let i = 0; i < 9; i++) {
    const c = box(player.x + 4, player.y, 1, 6).isColliding.char;
    if (c.a) {
      type = 0;
      player.y--;
    } else if (c.b) {
      type = 1;
      player.y--;
    } else if (c.c) {
      type = 2;
      player.y--;
    } else {
      if (type != null) {
        break;
      }
      player.y++;
    }
  }
  if (type === 0) {
    player.y += 4;
  }
  nextObjDist -= scr;
  if (nextObjDist < 0) {
    const type = rnd() < 0.5 ? "gold" : "spike";
    const distance = rnd(20, 60) / (type === "gold" ? 4 : 1.5);
    const wy = rnds(20) * (type === "gold" ? 1 : 1.5);
    objs.push({
      pos: vec(120, clamp(nextFloorPos.y + rnds(20), 10, 90)),
      d: distance / 2,
      distance,
      vy: rnds(1, sqrt(difficulty)) * 0.3,
      type,
    });
    nextObjDist = rnd(15, 25);
  }
  remove(objs, (o) => {
    o.pos.x -= scr;
    o.pos.y += o.vy;
    o.d -= abs(o.vy);
    if (o.d < 0) {
      o.d = o.distance;
      o.vy *= -1;
    }
    color(o.type === "gold" ? "yellow" : "red");
    const c = text(o.type === "gold" ? "$" : "x", o.pos).isColliding;
    if (o.distance > 4 && c.rect.purple) {
      return true;
    }
    if (c.char.d || c.char.e) {
      if (o.type === "gold") {
        play("coin");
        addScore(multiplier, o.pos);
        multiplier++;
        return true;
      } else {
        play("explosion");
        end();
      }
    }
    if (o.pos.x < -3) {
      if (o.type === "gold" && multiplier > 1) {
        play("hit");
        multiplier--;
      }
      return true;
    }
  });
}


<|start_filename|>docs/skyfloor/main.js<|end_filename|>
title = "SKY FLOOR";

description = `
[Slide] Move
`;

characters = [];

options = {
  theme: "pixel",
  isPlayingBgm: true,
  isReplayEnabled: true,
  isDrawingParticleFront: true,
  isDrawingScoreFront: true,
  seed: 100,
};

/** @type {{pos: Vector, size: Vector}[]} */
let floors;
let nextFloorDist;
/** @type {{pos: Vector, speed: number, angle: number, ticks: number}[]} */
let fans;
/** @type {Vector[]} */
let coins;
/** @type {{pos: Vector, vel: Vector, radius: number, isOut: boolean}} */
let ball;
/** @type {{pos: Vector, width: number, angle: number }[]} */
let lines;
let nextLineDist;
/** @type {Vector} */
let scr;
let multiplier;
const ballRadius = 2;

function update() {
  if (!ticks) {
    floors = [{ pos: vec(50, 50), size: vec(80, 150) }];
    fans = [];
    coins = [];
    nextFloorDist = 0;
    ball = { pos: vec(50, 90), vel: vec(), radius: ballRadius, isOut: false };
    lines = [];
    nextLineDist = 0;
    scr = vec();
    multiplier = 1;
  }
  scr.set();
  if (ball.pos.x < 45) {
    scr.x += (45 - ball.pos.x) * 0.1;
  } else if (ball.pos.x > 55) {
    scr.x += (55 - ball.pos.x) * 0.1;
  }
  if (ball.pos.y < 80) {
    scr.y += (80 - ball.pos.y) * 0.1;
  }
  nextLineDist -= scr.y * 0.2;
  if (nextLineDist < 0) {
    lines.push({
      pos: vec(rnd(99), -20),
      width: rnd(5, 20),
      angle: rnd() < 0.1 ? rnd(PI * 2) : (rndi(2) * PI) / 2,
    });
    nextLineDist += rnd(5, 10);
  }
  color("light_yellow");
  remove(lines, (l) => {
    l.pos.x += scr.x * 0.2;
    l.pos.y += scr.y * 0.2;
    bar(l.pos, l.width, 1, l.angle);
    return l.pos.y > 110;
  });
  nextFloorDist -= scr.y;
  if (nextFloorDist < 0) {
    const lf = floors[floors.length - 1];
    let x, w;
    for (let i = 0; i < 9; i++) {
      x = rnd(99);
      w = rnd(30, 80);
      if (abs(x - lf.pos.x) < (w + lf.size.x) / 2) {
        break;
      } else {
        x = lf.pos.x;
        w = lf.size.x;
      }
    }
    const h = rnd(30, 99);
    floors.push({ pos: vec(x, -h / 2 - 20), size: vec(w, h) });
    times(rndi(ceil(h / 30)), () => {
      const wy = rndi(2) * 2 - 1;
      fans.push({
        pos: vec(x + rnd(w * 0.6, w * 0.7) * wy, -h / 2 - 20 + rnds(h * 0.4)),
        speed: rnd(1, 2),
        angle: (wy === -1 ? 0 : PI) + rnds(PI / 8),
        ticks: 0,
      });
    });
    const cc = rndi(ceil(h / 10));
    let cy = rnds(h * 0.5) - (cc * 10) / 2;
    const cx = x + rnds(w * 0.2);
    times(cc, () => {
      if (cy > -h * 0.3 && cy < h * 0.3) {
        coins.push(vec(cx, cy - h / 2 - 20));
      }
      cy += 10;
    });
    nextFloorDist = rnd(h * 0.6, h * 0.8);
  }
  color("light_blue");
  remove(floors, (f) => {
    f.pos.add(scr);
    box(f.pos, f.size);
    return f.pos.y - f.size.y / 2 > 99;
  });
  color("black");
  remove(fans, (f) => {
    f.ticks += f.speed * f.speed;
    f.pos.add(scr);
    const oa = abs(wrap(f.pos.angleTo(ball.pos) - f.angle, -PI, PI));
    const d = f.pos.distanceTo(ball.pos) + 1;
    const fr = ((f.speed * clamp(PI / 4 - oa, 0, PI / 4)) / d) * 3;
    ball.vel.addWithAngle(f.angle, fr);
    box(vec(f.pos).addWithAngle(f.angle + PI, 5), 3);
    const c = bar(f.pos, cos(f.ticks * 0.1) * 9, 3, f.angle + PI / 2)
      .isColliding.rect;
    if (f.pos.y < -9 && (c.light_blue || c.black)) {
      return true;
    }
    particle(f.pos, f.speed * 0.2, f.speed * f.speed, f.angle, PI / 4);
    return f.pos.y > 105;
  });
  const o = input.pos.x - ball.pos.x;
  ball.vel.x += o * o * 0.0001 * (o < 0 ? -1 : 1);
  ball.vel.y -= 0.1;
  ball.vel.mul(0.95);
  ball.vel.y *= 1 - clamp(0.03 * sqrt(abs(o)), 0, 0.5);
  ball.pos.x += ball.vel.x * sqrt(difficulty);
  ball.pos.y += ball.vel.y * sqrt(difficulty);
  ball.pos.add(scr);
  ball.pos.y -= difficulty * 0.1;
  color("red");
  if (arc(ball.pos, ball.radius, 3).isColliding.rect.light_blue) {
    ball.isOut = false;
    ball.radius += (ballRadius - ball.radius) * 0.1;
  } else {
    play("hit");
    if (!ball.isOut) {
      ball.vel.x /= 2;
      ball.isOut = true;
    }
    ball.radius *= 0.925;
    if (ball.radius < 0.1) {
      play("explosion");
      end();
    }
  }
  color("blue");
  arc(ball.pos, 0.1, 3);
  color("yellow");
  remove(coins, (c) => {
    c.add(scr);
    if (box(c, 8).isColliding.rect.red) {
      play("coin");
      addScore(multiplier, c);
      particle(c);
      multiplier++;
      return true;
    }
    if (c.y > 105) {
      if (multiplier > 1) {
        multiplier--;
      }
      return true;
    }
  });
}


<|start_filename|>docs/futurewake/main.js<|end_filename|>
title = "FUTURE WAKE";

description = `
[Slide] Move
`;

characters = [];

options = {
  theme: "crt",
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 7,
};

/** @type {{pos: Vector, vel: Vector, speed: number}} */
let ballRoot;
/** @type {{pos: Vector, vel: Vector}} */
let ballWake;
let ballDist;
/** @type {Color}} */
let wakeColor;
let wakeDist;
let wakeCount;
let ballPos;
let isHittingRacket;
let multiplier;
const ballSize = 3;

function update() {
  if (!ticks) {
    ballRoot = {
      pos: vec(50, 50),
      vel: vec().addWithAngle(PI / 4, ballSize),
      speed: 1,
    };
    ballWake = {
      pos: vec(),
      vel: vec(),
    };
    wakeColor = "light_black";
    ballDist = wakeDist = wakeCount = 0;
    ballPos = vec();
    multiplier = 1;
    isHittingRacket = false;
  }
  color("light_blue");
  rect(0, 0, 7, 100);
  rect(93, 0, 7, 100);
  const rx = clamp(input.pos.x, 17, 83);
  color("blue");
  box(rx, 5, 20, 5);
  box(rx, 95, 20, 5);
  wakeColor = "light_black";
  wakeDist = 0;
  wakeCount = 99;
  ballDist += ballRoot.speed;
  ballWake.pos.set(ballRoot.pos);
  ballWake.vel.set(ballRoot.vel);
  isHittingRacket = false;
  while (wakeCount > 0) {
    updateBall();
    wakeCount--;
  }
  // @ts-ignore
  if (wakeColor === "light_green") {
    ballRoot.speed += difficulty;
  } else {
    ballRoot.speed = difficulty;
  }
  color("black");
  box(ballPos, ballSize);
  if (ballPos.y < 0 || ballPos.y > 99) {
    play("explosion");
    end();
  }
  if (isHittingRacket) {
    ballDist = 0;
    addScore(floor(multiplier), ballPos.x, clamp(ballPos.y, 30, 80));
    multiplier += 10;
    if (multiplier > 100) {
      ballRoot.vel.x += rnds(1);
      ballRoot.vel.normalize().mul(3);
    }
  }
  multiplier = clamp(multiplier * 0.99, 1, 100.99);
  text(`x${floor(multiplier)}`, 3, 9);

  function updateBall() {
    wakeDist += ballSize;
    if (wakeColor === "light_black" && wakeDist >= ballDist) {
      const r = (wakeDist - ballDist) / ballSize;
      ballPos.set(
        ballWake.pos.x + ballWake.vel.x * (1 - r),
        ballWake.pos.y + ballWake.vel.y * (1 - r)
      );
      wakeColor = "light_cyan";
    }
    if (
      wakeColor === "light_cyan" &&
      (ballWake.pos.y < 7 || ballWake.pos.y > 93)
    ) {
      wakeColor = "light_red";
      wakeCount = 5;
    }
    ballWake.pos.add(ballWake.vel);
    color(wakeColor);
    const c = box(ballWake.pos, ballSize).isColliding.rect;
    if (c.light_blue) {
      if (
        (ballWake.pos.x < 50 && ballWake.vel.x < 0) ||
        (ballWake.pos.x > 50 && ballWake.vel.x > 0)
      ) {
        ballWake.vel.x *= -1;
      }
    }
    if (
      c.blue &&
      ((ballWake.pos.y < 50 && ballWake.vel.y < 0) ||
        (ballWake.pos.y > 50 && ballWake.vel.y > 0))
    ) {
      const d = (ballWake.pos.x - rx) / 10;
      if (abs(d) < 0.6) {
        ballWake.vel.y *= -1;
      } else {
        let a = d > 0 ? PI / 4 + (1 - d) : (PI / 4) * 3 - (1 + d);
        if (ballWake.pos.y > 50) {
          a *= -1;
        }
        ballWake.vel.set().addWithAngle(a, ballSize);
      }
      if (wakeDist < ballDist) {
        play("hit");
        ballRoot.pos.set(ballWake.pos);
        ballRoot.vel.set(ballWake.vel);
        ballPos.set(ballWake.pos);
        isHittingRacket = true;
      }
      wakeColor = "light_green";
      wakeCount = 9;
    }
  }
}


<|start_filename|>docs/hyperlaser/main.js<|end_filename|>
title = "HYPER LASER";

description = `
[Hold] Laser
`;

characters = [
  `
LllllL
lLLLLl
lLLLLl
lLLLLl
lLLLLl
LllllL
`,
  `
  l
  l
ll ll
  l
  l
`,
  `
 yy
ylly
ylly
 yy
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 20,
};

/** @type {{pos: Vector, vel: Vector, ticks: number}[]} */
let lasers;
/** @type {Vector[]} */
let blocks;
let nextBlockDist;
/** @type {{pos: Vector, angle: number, laserTicks: number, hasShield: boolean}} */
let player;
/**
 * @type {{
 * pos: Vector, angle: number, targetAngle: number, turretAngle: number,
 * shotAngle: number, burstTicks: number, burstIndex: number, burstCount: number
 * shotTicks: number, shotInterval: number
 * turnTicks: number
 * }[]}
 */
let enemies;
/** @type {{pos: Vector, vel: Vector}[]} */
let shots;
let nextEnemyDist;
let multiplier;

function update() {
  if (!ticks) {
    lasers = [];
    blocks = [];
    nextBlockDist = 0;
    player = {
      pos: vec(50, 95),
      angle: -PI / 2,
      laserTicks: 0,
      hasShield: false,
    };
    enemies = [];
    shots = [];
    nextEnemyDist = 0;
    multiplier = 1;
  }
  color("light_cyan");
  rect(0, 0, 5, 100);
  rect(95, 0, 5, 100);
  let scr = difficulty * 0.1;
  nextBlockDist -= scr;
  if (nextBlockDist < 0) {
    blocks.push(vec(rnd(8, 92), -9));
    nextBlockDist = rnd(16);
  }
  color("red");
  const sp = input.pos.clamp(1, 99, 1, 99);
  char("b", sp);
  const ta = player.pos.angleTo(sp);
  if (input.isPressed) {
    player.hasShield = false;
    player.laserTicks--;
    if (player.laserTicks < 0) {
      play("laser");
      lasers.push({
        pos: vec(player.pos),
        vel: vec().addWithAngle(ta, sqrt(difficulty) * 3),
        ticks: 0,
      });
      player.laserTicks = 9 / sqrt(difficulty);
    }
  } else {
    player.hasShield = true;
    const x = clamp(input.pos.x, 7, 93);
    const s = sqrt(difficulty) * 0.3;
    if (player.pos.x < x - 1) {
      player.pos.x += s;
      player.angle += wrap(-player.angle, -PI, PI) * 0.1;
    }
    if (player.pos.x > x + 1) {
      player.pos.x -= s;
      player.angle += wrap(PI - player.angle, -PI, PI) * 0.1;
    }
    color("light_blue");
    arc(player.pos, 5, 2, ticks * 0.03, ticks * 0.1 + PI);
    arc(player.pos, 5, 2, ticks * 0.03 + PI, ticks * 0.1 + PI * 2);
  }
  color("black");
  bar(player.pos, 1, 3, player.angle);
  const p = vec(player.pos);
  color("light_black");
  p.addWithAngle(player.angle + PI / 2, 2);
  bar(p, 4, 2, player.angle);
  p.addWithAngle(player.angle + PI / 2, -4);
  bar(p, 4, 2, player.angle);
  color("red");
  bar(player.pos, 2, 2, ta, -0.5);
  color("black");
  remove(blocks, (b) => {
    b.y += scr;
    const c = char("a", b).isColliding;
    if (c.rect.black) {
      particle(b);
      return true;
    }
    return (c.char.a && b.y < -3) || b.y > 103;
  });
  remove(lasers, (l) => {
    l.pos.y += scr;
    const pp = vec(l.pos);
    color("transparent");
    l.pos.add(l.vel);
    let isColliding = false;
    if (
      bar(l.pos.x, pp.y, 5, 2, l.vel.angle).isColliding.char.a ||
      (l.pos.x < 5 && l.vel.x < 0) ||
      (l.pos.x > 95 && l.vel.x > 0)
    ) {
      l.vel.x *= -1;
      isColliding = true;
    }
    if (bar(pp.x, l.pos.y, 5, 2, l.vel.angle).isColliding.char.a) {
      l.vel.y *= -1;
      isColliding = true;
    }
    if (
      !isColliding &&
      bar(l.pos.x, l.pos.y, 5, 2, l.vel.angle).isColliding.char.a
    ) {
      l.vel.x *= -1;
      l.vel.y *= -1;
      isColliding = true;
    }
    if (isColliding) {
      l.pos.set(pp);
      l.pos.add(l.vel);
    }
    // @ts-ignore
    color(["blue", "purple", "cyan", "green"][l.ticks % 4]);
    l.ticks++;
    const c = bar(l.pos, 5, 2, l.vel.angle).isColliding;
    if (c.char.a) {
      return true;
    }
    return l.pos.y < -5 || l.pos.y > 105 || l.ticks > 120 / sqrt(difficulty);
  });
  if (enemies.length === 0) {
    nextBlockDist = 0;
  }
  nextEnemyDist--;
  if (nextEnemyDist < 0) {
    const shotInterval = rnd(60, 120) / sqrt(difficulty);
    enemies.push({
      pos: vec(rnd(10, 90), -9),
      angle: PI / 2,
      targetAngle: PI / 2,
      shotAngle: PI / 2,
      turretAngle: PI / 2,
      burstTicks: 9999,
      burstIndex: 0,
      burstCount: rndi(3, 9),
      shotTicks: rnd(shotInterval),
      shotInterval,
      turnTicks: 0,
    });
    nextEnemyDist = rnd(150, 200) / sqrt(difficulty);
  }
  remove(enemies, (e) => {
    e.pos.y += scr;
    const pp = vec(e.pos);
    let isMoving = false;
    if (abs(wrap(e.angle - e.targetAngle, -PI, PI)) < 0.1) {
      e.angle = e.targetAngle;
      e.pos.addWithAngle(e.angle, sqrt(difficulty) * 0.1);
      isMoving = true;
      e.turnTicks--;
    } else {
      e.angle += wrap(e.targetAngle - e.angle, -PI, PI) * 0.1;
    }
    color("yellow");
    const c = bar(e.pos, 2, 3, e.angle).isColliding;
    const p = vec(e.pos);
    color("light_black");
    p.addWithAngle(e.angle + PI / 2, 2);
    bar(p, 4, 1, e.angle);
    p.addWithAngle(e.angle + PI / 2, -4);
    bar(p, 4, 1, e.angle);
    e.shotTicks--;
    let hasShield = e.shotTicks > 0 && e.pos.y < 70;
    if (hasShield) {
      e.turretAngle = (round(e.pos.angleTo(player.pos) / (PI / 4)) * PI) / 4;
    }
    color("red");
    bar(e.pos, 2, 2, e.turretAngle, -0.5);
    if (e.shotTicks < 0 && e.burstIndex <= 0) {
      e.burstTicks = 0;
      e.burstIndex = e.burstCount;
    }
    e.burstTicks--;
    if (e.burstTicks < 0) {
      shots.push({
        pos: vec(e.pos),
        vel: vec().addWithAngle(e.turretAngle, sqrt(difficulty) * 2),
      });
      e.burstIndex--;
      if (e.burstIndex > 0) {
        e.burstTicks = 20 / sqrt(difficulty);
      } else {
        e.burstTicks = 9999;
        e.shotTicks = e.shotInterval;
      }
    }
    if (
      isMoving &&
      (c.char.a || e.turnTicks < 0 || e.pos.x < 9 || e.pos.x > 90)
    ) {
      e.pos.set(pp);
      e.targetAngle = wrap(
        e.targetAngle + ((rndi(2) * 2 - 1) * PI) / 2,
        -PI,
        PI
      );
      e.turnTicks = rnd(200, 300) / sqrt(difficulty);
    }
    color("light_yellow");
    if (hasShield) {
      arc(e.pos, 4, 2, ticks * 0.03, ticks * 0.1 + PI);
      arc(e.pos, 4, 2, ticks * 0.03 + PI, ticks * 0.1 + PI * 2);
    } else if (c.rect.blue || c.rect.purple || c.rect.cyan || c.rect.green) {
      play("explosion");
      addScore(multiplier * 10, e.pos);
      color("red");
      particle(e.pos, 19, 2);
      return true;
    }
    if (e.pos.y > 99) {
      color("red");
      text("X", e.pos.x, 96);
      play("lucky");
      end();
    }
  });
  color("transparent");
  remove(blocks, (b) => {
    return char("a", b).isColliding.rect.yellow && b.y < -3;
  });
  remove(lasers, (l) => {
    if (bar(l.pos, 5, 2, l.vel.angle).isColliding.rect.light_yellow) {
      play("hit");
      return true;
    }
  });
  color("black");
  remove(shots, (s) => {
    s.pos.y += scr;
    s.pos.add(s.vel);
    const c = char("c", s.pos).isColliding;
    if (!player.hasShield && c.rect.black) {
      play("lucky");
      end();
    }
    if (player.hasShield && c.rect.light_blue) {
      play("powerUp");
      addScore(multiplier, player.pos);
      multiplier++;
      return true;
    }
    return !s.pos.isInRect(5, 0, 90, 100) || c.char.a;
  });
}


<|start_filename|>docs/cardq/main.js<|end_filename|>
title = "CARD Q";

description = `
[Tap]
 Pull out a card
`;

characters = [
  `
l  l
l l l
l l l
l l l
l l l
l  l
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 3,
};

const numChars = [
  "",
  "A",
  "2",
  "3",
  "4",
  "5",
  "6",
  "7",
  "8",
  "9",
  "10",
  "J",
  "Q",
  "K",
];

/** @type {{num: number, pos: Vector, tPos:Vector, gPos:Vector }[]} */
let playerCards;
/** @type {{num: number, pos: Vector, tPos:Vector, gPos:Vector }[]} */
let enemyCards;
/** @type {{num: number, pos: Vector, tPos:Vector }[]} */
let placedCards;
/** @type { number[]} */
let placedCardNumbers;
let centerY;
let targetCenterY;
let playerPrevMoveIndex;
let enemyPrevMoveIndex;
let enemyNextMoveIndex;
let enemyNextMoveTicks;
let shuffleTicks;
let shuffleCount;
let penaltyIndex;
let penaltyTicks;
let multiplier;
const cardIntervalX = 15;
const cardRowCount = 5;
const cardColumnCount = 5;

function update() {
  if (!ticks) {
    placedCardNumbers = [2, 12];
    placedCards = times(2, (i) => {
      const pos = vec(calcPlacedCardX(i), 0);
      const tPos = vec(pos);
      return { num: placedCardNumbers[i], pos, tPos };
    });
    playerCards = times(cardColumnCount * cardRowCount, (i) => {
      const gPos = vec(i % cardColumnCount, floor(i / cardColumnCount));
      const num = gPos.y === 0 ? [1, 3, 3, 11, 13][gPos.x] : rndi(1, 14);
      const pos = calcPlayerCardPos(gPos);
      const tPos = vec(pos);
      return { num, pos, tPos, gPos };
    });
    enemyCards = times(cardColumnCount * cardRowCount, (i) => {
      const gPos = vec(i % cardColumnCount, floor(i / cardColumnCount));
      const num = rndi(1, 14);
      const pos = calcEnemyCardPos(gPos);
      const tPos = vec(pos);
      return { num, pos, tPos, gPos };
    });
    centerY = targetCenterY = 40;
    playerPrevMoveIndex = enemyPrevMoveIndex = 0;
    enemyNextMoveIndex = undefined;
    enemyNextMoveTicks = 120;
    shuffleTicks = shuffleCount = 0;
    penaltyTicks = -1;
    multiplier = 1;
  }
  shuffleTicks++;
  if (shuffleTicks > 60) {
    let isPlacable = false;
    let isPlayerPlacable = false;
    for (let i = 0; i < cardColumnCount; i++) {
      const [pi, cn, ci] = checkPlacedIndex(
        i,
        playerPrevMoveIndex,
        playerCards
      );
      if (pi >= 0) {
        isPlacable = isPlacable = true;
        break;
      }
      const [epi, ecn, eci] = checkPlacedIndex(
        i,
        enemyPrevMoveIndex,
        enemyCards
      );
      if (epi >= 0) {
        isPlacable = true;
        break;
      }
    }
    if (!isPlayerPlacable) {
      enemyNextMoveTicks *= 0.3;
    }
    shuffleCount++;
    if (!isPlacable || shuffleCount > 2) {
      play("powerUp");
      placedCards.forEach((c) => {
        c.tPos.x = c.pos.x < 50 ? -50 : 150;
      });
      placedCardNumbers = times(2, () => rndi(1, 14));
      placedCardNumbers.forEach((n, i) => {
        placedCards.push({
          num: n,
          pos: vec(i === 0 ? -5 : 105, 0),
          tPos: vec(calcPlacedCardX(i), 0),
        });
      });
      shuffleCount = 0;
    }
    shuffleTicks = 0;
  }
  const pci = floor((input.pos.x - 50) / cardIntervalX + cardColumnCount / 2);
  if (input.isJustPressed) {
    if (pci >= 0 && pci < cardColumnCount) {
      const pi = placeCard(pci, playerPrevMoveIndex, playerCards);
      if (pi < 0) {
        play("hit");
        penaltyIndex = pci;
        penaltyTicks = 60;
        targetCenterY += 5;
        multiplier = 1;
        shuffleTicks = shuffleCount = 0;
      } else {
        play("coin");
        playerPrevMoveIndex = pi;
        targetCenterY -= 5;
        addScore(multiplier, pi === 0 ? 8 : 92, centerY);
        multiplier++;
      }
    }
  }
  enemyNextMoveTicks--;
  if (enemyNextMoveTicks < 0) {
    enemyNextMoveTicks = rnd(50, 70) / sqrt(difficulty);
    if (enemyNextMoveIndex != null) {
      const [pi, cn, ci] = checkPlacedIndex(
        enemyNextMoveIndex,
        enemyPrevMoveIndex,
        enemyCards
      );
      if (pi < 0) {
        enemyNextMoveTicks *= 3;
      } else {
        play("select");
        placeCard(enemyNextMoveIndex, enemyPrevMoveIndex, enemyCards);
        enemyPrevMoveIndex = pi;
        targetCenterY += 5;
        multiplier = 1;
      }
    }
    enemyNextMoveIndex = undefined;
    let ni = rndi(cardColumnCount);
    for (let i = 0; i < cardColumnCount; i++) {
      const [pi, cn, ci] = checkPlacedIndex(ni, enemyPrevMoveIndex, enemyCards);
      if (pi >= 0) {
        if (pi !== enemyPrevMoveIndex) {
          enemyNextMoveTicks *= 1.5;
        }
        enemyNextMoveIndex = ni;
        break;
      }
      ni = wrap(ni + 1, 0, cardColumnCount);
    }
  }
  centerY += (targetCenterY - centerY) * 0.1;
  playerCards.forEach((c) => {
    movePos(c.pos, c.tPos, 0.2);
    const ec = c.gPos.y === 0 && c.gPos.x === pci ? "green" : undefined;
    drawCard(c.pos.x, c.pos.y + centerY, c.num, c.gPos.y, ec);
  });
  enemyCards.forEach((c) => {
    movePos(c.pos, c.tPos, 0.2);
    const ec =
      c.gPos.y === 0 && c.gPos.x === enemyNextMoveIndex ? "red" : undefined;
    drawCard(c.pos.x, c.pos.y + centerY, c.num, c.gPos.y, ec);
  });
  placedCards.forEach((c) => {
    movePos(c.pos, c.tPos, 0.2);
    drawCard(c.pos.x, c.pos.y + centerY, c.num, 0);
  });
  if (placedCards.length > 19) {
    placedCards.shift();
  }
  if (penaltyTicks > 0) {
    penaltyTicks--;
    color("red");
    text("X", calcCardX(penaltyIndex), centerY + 6);
    color("black");
  }
  if (targetCenterY < 16) {
    targetCenterY += (16 - targetCenterY) * 0.1;
  }
  if (centerY > 94) {
    play("explosion");
    end();
  }

  function placeCard(idx, ppi, cards) {
    const [pi, cn, ci] = checkPlacedIndex(idx, ppi, cards);
    if (pi === -1) {
      return -1;
    }
    placedCardNumbers[pi] = cn;
    const c = cards.splice(ci, 1)[0];
    placedCards.push({
      num: c.num,
      pos: c.pos,
      tPos: vec(calcPlacedCardX(pi), 0),
    });
    cards.forEach((c) => {
      if (c.gPos.x === idx) {
        c.gPos.y--;
        c.tPos =
          cards === playerCards
            ? calcPlayerCardPos(c.gPos)
            : calcEnemyCardPos(c.gPos);
      }
    });
    const gPos = vec(idx, cardRowCount - 1);
    const pos =
      cards === playerCards ? calcPlayerCardPos(gPos) : calcEnemyCardPos(gPos);
    const tPos = vec(pos);
    cards.push({ num: rndi(1, 14), pos, tPos, gPos });
    shuffleTicks = shuffleCount = 0;
    return pi;
  }

  function checkPlacedIndex(idx, ppi, cards) {
    let cn;
    let ci;
    cards.forEach((c, i) => {
      if (c.gPos.y === 0 && c.gPos.x === idx) {
        cn = c.num;
        ci = i;
      }
    });
    let pi = -1;
    placedCardNumbers.forEach((c, i) => {
      if (
        (ppi === 1 || pi === -1) &&
        (cn === wrap(c - 1, 1, 14) || cn === wrap(c + 1, 1, 14))
      ) {
        pi = i;
      }
    });
    return [pi, cn, ci];
  }

  function drawCard(x, y, n, gy, edgeColor = undefined) {
    if (y < -5 || y > 105) {
      return;
    }
    const ec =
      edgeColor != null ? edgeColor : gy === 0 ? "black" : "light_black";
    color(ec);
    box(x, y, 9, 10);
    color("white");
    box(x, y, 7, 8);
    const nc = gy === 0 ? "black" : "light_black";
    color(nc);
    if (n === 10) {
      char("a", x, y);
    } else {
      text(numChars[n], x, y);
    }
  }

  function calcPlayerCardPos(p) {
    return vec(calcCardX(p.x), (p.y + 1) * 11);
  }

  function calcEnemyCardPos(p) {
    return vec(calcCardX(p.x), (p.y + 1) * -11);
  }

  function calcPlacedCardX(i) {
    return 50 + (i - 0.5) * 25;
  }

  function calcCardX(i) {
    return 50.5 + (i - cardColumnCount / 2 + 0.5) * cardIntervalX;
  }

  function movePos(p, tp, ratio) {
    p.add(vec(tp).sub(p).mul(ratio));
    if (p.distanceTo(tp) < 1) {
      p.set(tp);
    }
  }
}


<|start_filename|>docs/rolfrg/main.js<|end_filename|>
title = "ROLFRG";

description = `
[Press] Turn
`;

characters = [];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 1,
};

let x;
let w;
let frogs;
let l;

function update() {
  if (!ticks) {
    frogs = times(5, (i) => {
      return { p: vec(0, -i * 9), a: rnd(9) };
    });
    x = 50;
    w = 1;
  }
  color("green");
  score++;
  l = ticks % ceil(30 / difficulty) ? 4 : 30;
  frogs.map((f) => {
    if (f.p.y < 0) {
      f.p.x = rnd(99);
      f.p.y += rnd(99, 120);
    }
    bar(f.p, l, 3, f.a, 0);
    f.a += difficulty / 9;
    f.p.y -= difficulty / 2;
    if (l > 9) {
      play("laser");
      f.p.addWithAngle(f.a, 30);
    }
  });
  color("yellow");
  x += w * difficulty;
  if (input.isJustPressed || x < 0 || x > 99) {
    w *= -1;
  }
  if (box(x, 36, 3).isColliding.rect.green) {
    play("lucky");
    end();
  }
}


<|start_filename|>docs/gloop/main.js<|end_filename|>
title = "GLOOP";

description = `
[Slide] Move
`;

characters = [
  `
 llll
llllll
ll  ll
ll  ll
`,
  `
 lll
l   l
l l l
l   l
 lll
`,
  `
l l
 l
l l
`,
  `
 l l l
l   l
 l   l
l   l
 l   l
l l l
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
};

let p, v, vya, pis, sps;

function update() {
  if (ticks === 0) {
    p = vec(50, 50);
    v = vec();
    vya = -1;
    pis = [];
    sps = [];
  }
  color("light_cyan");
  for (let x = 0; x < 15; x++) {
    for (let y = 0; y < 18; y++) {
      if ((x + y) % 2 === 0) {
        char("d", x * 6 + 8, y * 6);
      }
    }
  }
  v.y += 0.02 * difficulty * vya;
  v.mul(0.99);
  p.add(v);
  p.x = clamp(input.pos.x, 8, 92);
  p.y = wrap(p.y, 0, 99);
  color("black");
  char("a", p);
  while (pis.length < 7) {
    pis.push({
      p: vec(rnd(10, 90), rnd(10, 90)),
    });
  }
  if (rnd() < 0.02 * difficulty) {
    const pp = vec(rnd(10, 90), rnd(10, 90));
    if (abs(pp.x - p.x) + abs(wrap(pp.y - p.y, -50, 50)) > 25) {
      sps.push({
        p: pp,
        isAlive: true,
      });
    }
  }
  pis = pis.filter((pi) => {
    let isAlive = true;
    if (char("b", pi.p).isColliding.char.a) {
      if (abs(v.y) > 1) {
        play("select");
        isAlive = false;
        sps.map((sp) => {
          if (sp.p.distanceTo(pi.p) < 20) {
            play("coin");
            sp.isAlive = false;
          }
        });
      } else {
        play("hit");
      }
      v.y *= -0.3;
      vya *= -1;
      p.y = pi.p.y + vya * 5;
    }
    return isAlive;
  });
  let sc = 1;
  color("red");
  sps = sps.filter((sp) => {
    if (sp.isAlive) {
      if (char("c", sp.p).isColliding.char.a) {
        play("explosion");
        end();
      }
      return true;
    } else {
      addScore(sc, sp.p);
      sc++;
    }
  });
}


<|start_filename|>docs/floors5/main.js<|end_filename|>
title = "FLOORS 5";

description = `
[Tap]  Jump / Double Jump
[Hold] Fly
`;

characters = [
  `



 l  l
l ll l
 l  l
`,
  `
 lll
l l ll
llllll
l ll l


`,
  `
 llll
llllll
llllll
llllll
llllll
 llll
`,
  `
 llll
l    l
l    l
l    l
l    l
 llll
`,
];

options = {
  viewSize: { x: 200, y: 100 },
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 7,
};

/**
 * @type { {pos: Vector, width: number, index: number,
 *  paintFrom: number, paintTo:number }[] }
 */
let floors;
/** @type { Color[] } */
const colors = ["red", "green", "blue", "yellow", "purple"];
/**
 * @type { {pos:Vector, vel: Vector, floor: any,
 * by: number, bvy: number, fallTicks: number, jumpCount: number }}
 */
let car;
let landedColors;
let multiplier;

function update() {
  if (!ticks) {
    floors = times(5, (i) => {
      return {
        pos: vec([25, 52, 105, 160, 220][i], [30, 50, 70, 60, 40][i]),
        width: [10, 35, 30, 30, 50][i],
        index: i,
        paintFrom: 0,
        paintTo: 0,
      };
    });
    car = {
      pos: vec(10, 10),
      vel: vec(1, 0),
      floor: undefined,
      by: 0,
      bvy: 0,
      fallTicks: -99,
      jumpCount: 0,
    };
    landedColors = times(5, () => false);
    multiplier = 1;
  }
  floors.forEach((f, i) => {
    if (f.pos.x + f.width < 0) {
      f.pos.set(rnd(200, 250), rnd(30, 90));
      f.width = rnd(20, 60);
      f.paintFrom = f.paintTo = 0;
    }
    color(colors[i]);
    f.pos.x -= car.vel.x;
    rect(f.pos, f.width, 6);
    color("white");
    rect(f.pos.x + 1, f.pos.y + 1, f.width - 2, 4);
    color(colors[i]);
    rect(f.pos.x + f.paintFrom, f.pos.y + 1, f.paintTo - f.paintFrom, 4);
  });
  car.vel.x += difficulty * 0.02;
  if (car.floor == null) {
    car.vel.y += input.isPressed ? 0.03 : 0.18;
  }
  car.pos.y += car.vel.y;
  car.bvy -= car.by * 0.1;
  car.by += car.bvy;
  car.by *= 0.9;
  car.fallTicks--;
  color("black");
  const cr = char("a", car.pos.x, clamp(car.pos.y, 0, 999)).isColliding.rect;
  const crb = char("b", car.pos.x, car.pos.y + car.by).isColliding.rect;
  if (car.floor == null) {
    colors.forEach((c, i) => {
      if (cr[c] || crb[c]) {
        if (car.vel.y >= 0) {
          play("select");
          car.floor = floors[i];
          car.pos.y = car.floor.pos.y - 3;
          car.vel.y = 0;
          car.vel.x = sqrt(difficulty);
          car.floor.paintFrom = clamp(car.pos.x - 5 - car.floor.pos.x, 0, 999);
          car.jumpCount = 0;
          landedColors[i] = true;
        } else {
          play("hit");
          car.pos.y = floors[i].pos.y + 9 - car.by;
          car.vel.y *= -0.7;
        }
      }
    });
    if (
      car.floor == null &&
      (car.fallTicks > -9 || car.jumpCount < 2) &&
      input.isJustPressed
    ) {
      play("jump");
      car.vel.y = -2;
      car.vel.x = sqrt(difficulty);
      car.bvy = -2;
      car.jumpCount++;
    }
  } else {
    if (input.isJustPressed) {
      play("jump");
      addFloorScore(car.floor);
      car.floor.pos.x = -999;
      car.floor = undefined;
      car.vel.y = -2;
      car.vel.x = sqrt(difficulty);
      car.bvy = -2;
      car.jumpCount++;
    } else if (car.floor.pos.x + car.floor.width < car.pos.x - 3) {
      addFloorScore(car.floor);
      car.floor.pos.x = -999;
      car.floor = undefined;
      car.vel.x = sqrt(difficulty);
      car.fallTicks = 0;
      car.jumpCount = 0;
    } else {
      car.floor.paintTo = clamp(
        car.pos.x + 5 - car.floor.pos.x,
        0,
        car.floor.width
      );
    }
  }
  let isAll = true;
  landedColors.forEach((lc, i) => {
    color(colors[i]);
    char(lc ? "c" : "d", i * 7 + 3, 96);
    isAll = lc && isAll;
  });
  if (isAll) {
    play("coin");
    multiplier++;
    landedColors = times(5, () => false);
  }
  if (multiplier > 1) {
    color("black");
    text(`x${multiplier}`, 45, 96);
  }
  if (car.pos.y > 99) {
    play("explosion");
    end();
  }

  function addFloorScore(f) {
    play("powerUp");
    const w = f.paintTo - f.paintFrom;
    const m = w >= f.width ? 3 : 1;
    let y = f.pos.y - (m > 1 ? 7 : 0);
    const s = clamp(floor(w) * multiplier, 0, 999);
    for (let i = 0; i < m; i++) {
      addScore(s, f.pos.x + f.width + 15, y);
      y += 7;
    }
  }
}


<|start_filename|>docs/refbals/main.js<|end_filename|>
// Title of the game
title = "REFBALS";

// Description is displayed on the title screen
description = `
[Hold] Accel
`;

// User defined text characters
characters = [];

// Game options
options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
};

let balls;
let walls;

// 'update()' is called per frame (1 frame = 1/60 second)
function update() {
  // 'ticks' counts the number of frames from the start of the game
  if (!ticks) {
    // Initialize variables at the first frame (ticks === 0)
    balls = [];
    // 'vec()' creates a 2d vector instance
    walls = times(5, (i) => vec(i * -29, -9));
  }
  if (!(ticks % 99)) {
    // 'rnd()' returns a random number
    balls.push({ p: vec(rnd(50), 0), v: 0 });
  }
  // 'color()' sets a drawing color
  color("blue");
  walls.map((w) => {
    // 'input.isPressed' returns true if
    // a mouse button, a key or a touch screen is pressed
    w.x -= input.isPressed ? 2 : 1;
    // 'box()' draws a rectangle
    box(w, 36, 3);
    if (w.x < -19) {
      w.x += rnd(130, 150);
      w.y = rnd(50, 90);
    }
  });
  color("purple");
  balls.map((b) => {
    if ((b.p.y += b.v += 0.03) > 99) {
      // 'play()' plays a sound effect
      play("explosion");
      // A game is over when 'end()' is called
      end();
    }
    // 'box()' returns a collision status
    if (box(b.p, 5).isColliding.rect.blue) {
      play("select");
      // 'score' represents the score of the game
      score++;
      b.p.y += (b.v *= -1) * 2;
    }
  });
}


<|start_filename|>docs/darkcave/main.js<|end_filename|>
title = "DARK CAVE";

description = `
[Slide] Move
`;

characters = [];

options = {
  theme: "dark",
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 6,
};

/** @type {{pos: Vector, type: "spike" | "coin"}[]} */
let objs;
let nextSpikeDist;
let nextCoinDist;
/** @type {{pos: Vector, angle: number, range: number}} */
let player;
let pressedTicks;
const angleWidth = PI / 6;

function update() {
  if (!ticks) {
    objs = [];
    nextSpikeDist = 0;
    nextCoinDist = 10;
    pressedTicks = 0;
    player = { pos: vec(50, 90), angle: 0, range: 40 };
  }
  const scr = difficulty * (0.1 + player.range * 0.01);
  nextSpikeDist -= scr;
  if (nextSpikeDist < 0) {
    objs.push({ pos: vec(rnd(5, 95)), type: "spike" });
    nextSpikeDist = rnd(20, 30);
  }
  nextCoinDist -= scr;
  if (nextCoinDist < 0) {
    objs.push({ pos: vec(rnd(10, 90)), type: "coin" });
    nextCoinDist = rnd(30, 40);
  }
  const pp = player.pos.x;
  player.pos.x = clamp(input.pos.x, 3, 97);
  const vx = clamp(player.pos.x - pp, -0.1, 0.1);
  player.angle = clamp(player.angle + vx * sqrt(difficulty), -PI / 8, PI / 8);
  player.range *= 1 - 0.001 * sqrt(difficulty);
  color("white");
  box(player.pos, 7);
  remove(objs, (o) => {
    o.pos.y += scr;
    const d = player.pos.distanceTo(o.pos);
    const a = player.pos.angleTo(o.pos) + PI / 2;
    if (abs(a - player.angle) < angleWidth) {
      color(o.type === "coin" ? "yellow" : "purple");
      if (d < player.range) {
        text(o.type === "coin" ? "$" : "*", o.pos);
      } else {
        particle(o.pos, 0.05);
      }
    }
    color("transparent");
    const c = text(o.type === "coin" ? "$" : "*", o.pos).isColliding;
    if (c.rect.white) {
      if (o.type === "coin") {
        play("powerUp");
        addScore(floor(player.range), o.pos);
        player.range = clamp(player.range + 9, 9, 99);
      } else {
        play("explosion");
        color("red");
        text("*", o.pos);
        particle(o.pos);
        player.range /= 3;
      }
      return true;
    }
  });
  color("black");
  if (player.range < 9) {
    color("red");
  }
  const a1 = player.angle - angleWidth - PI / 2;
  const a2 = a1 + angleWidth * 2;
  line(player.pos, vec(player.pos).addWithAngle(a1, player.range), 2);
  line(player.pos, vec(player.pos).addWithAngle(a2, player.range), 2);
  arc(player.pos, player.range, 2, a1, a2);
  if (player.range < 9) {
    play("lucky");
    end();
  }
}


<|start_filename|>docs/pfit/main.js<|end_filename|>
title = "P FIT";

description = `
[Slide] Move
`;

characters = [];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 22,
};

/**
 * @type {{
 * pos: Vector, width: number, height: number, targetY: number,
 * type: "fix" | "move" | "end"
 * }[]}
 */
let walls;
let isAddingWall;
let wallX;
let matchTicks;
let nextScore;
let wallCount;
let wallWidth;

function update() {
  if (!ticks) {
    walls = [];
    isAddingWall = true;
    wallX = 0;
    matchTicks = 0;
    nextScore = 0;
  }
  if (isAddingWall) {
    wallCount = clamp(floor(6 * difficulty * rnd(0.5, 1)), 6, 20);
    wallWidth = 100 / (wallCount - 1);
    let h = rnd(22, 32);
    let hs = times(wallCount, () => {
      h += rnd(10, 50) / wallCount;
      return h;
    });
    times(99, () => {
      let i1 = rndi(wallCount);
      let i2 = rndi(wallCount);
      const t = hs[i1];
      hs[i1] = hs[i2];
      hs[i2] = t;
    });
    times(wallCount * 2, (i) => {
      walls.push({
        pos: vec(
          (i % wallCount) * wallWidth - wallWidth / 2,
          i < wallCount ? 75 : 225
        ),
        targetY: i < wallCount ? -25 : 125,
        width: wallWidth,
        height: i < wallCount ? hs[i] : hs[i - wallCount] - 99,
        type: i < wallCount ? "fix" : "move",
      });
    });
    wallX = -rnd(10, 90);
    for (let i = 0; i < 9; i++) {
      if (
        abs(
          wrap(
            wallX + input.pos.x,
            (-wallWidth * wallCount) / 2,
            (wallWidth * wallCount) / 2
          )
        ) > 20
      ) {
        break;
      }
      wallX = -rnd(10, 90);
    }
    nextScore = 300;
    isAddingWall = false;
  }
  if (matchTicks <= 30) {
    const wi = wrap(floor((input.pos.x - 5) / 10), 0, 11);
  }
  remove(walls, (w, i) => {
    w.pos.y += (w.targetY - w.pos.y) * 0.1;
    if (w.type === "end") {
      if (w.pos.y + (w.height > 0 ? w.height : 0) < 1) {
        return true;
      }
    } else {
      w.targetY += (w.type === "fix" ? 1 : -1) * difficulty * 0.02;
    }
    color(w.type === "move" && matchTicks < 30 ? "blue" : "green");
    const x =
      w.type === "move" && matchTicks <= 30
        ? wrap(w.pos.x + wallX + input.pos.x, -wallWidth, 100)
        : w.pos.x;
    if (
      rect(x, w.pos.y, ceil(w.width), w.height).isColliding.rect.green &&
      matchTicks < 30 &&
      w.type === "move"
    ) {
      color("red");
      text("x", x + wallWidth / 2, wrap(w.pos.y + w.height, -wallWidth, 100));
      color("blue");
      play("explosion");
      end();
    }
  });
  if (matchTicks > 30) {
    matchTicks++;
    if (matchTicks == 60) {
      walls.forEach((w) => {
        w.targetY = w.type === "fix" ? -100 : 0;
        w.type = "end";
      });
      isAddingWall = true;
      matchTicks = 0;
    }
  } else if (
    abs(
      wrap(
        wallX + input.pos.x,
        (-wallWidth * wallCount) / 2,
        (wallWidth * wallCount) / 2
      )
    ) < 3
  ) {
    matchTicks++;
    if (matchTicks > 30) {
      play("powerUp");
      walls.forEach((w) => {
        w.targetY = w.type === "fix" ? 1 : 99;
      });
      addScore(nextScore);
    }
  } else {
    matchTicks = 0;
  }
  color("black");
  text(`+${nextScore}`, 3, 10);
  if (matchTicks <= 30 && nextScore > 0) {
    nextScore--;
  }
}


<|start_filename|>docs/forfour/main.js<|end_filename|>
title = "FORFOUR";

description = `
[Tap] Roll
`;

characters = [
  `
 llll
llllll
ll  ll
ll  ll
llllll
 llll
`,
];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 2,
};

let circles;
let tmpCircles;
let flying;
let flyingColor;
let flyingAngle;
let flyingCount;

function update() {
  const circleCount = 13;
  const circlesOffset = vec(12, 18);
  const cc = floor(circleCount / 2);
  /** @type {Color[]} */
  const colors = ["black", "red", "green", "blue", "yellow"];
  /** @type {Color[]} */
  const lightColors = [
    "light_black",
    "light_red",
    "light_green",
    "light_blue",
    "light_yellow",
  ];
  if (!ticks) {
    circles = times(circleCount, () => times(circleCount, () => -1));
    tmpCircles = times(circleCount, () => times(circleCount, () => -1));
    circles[cc][cc] = 0;
    for (let i = cc + 1; i < circleCount - 1; i++) {
      circles[i][cc] = rndi(1, 5);
    }
    flying = vec();
    flyingCount = -999;
  }
  if (flyingCount < -900) {
    flyingCount = 60 / difficulty;
    flyingAngle = rndi(4);
    flyingColor = rndi(1, 5);
    let sz = checkSize();
    if (rnd() < sqrt(sz) * 0.1) {
      flyingColor = 0;
      sz = ceil(sz * 0.7);
    }
    const min = cc - sz + 1;
    const max = cc + sz - 1;
    switch (flyingAngle) {
      case 0:
        flying.set(-1, rndi(min, max + 1));
        break;
      case 1:
        flying.set(rndi(min, max + 1), -1);
        break;
      case 2:
        flying.set(circleCount, rndi(min, max + 1));
        break;
      case 3:
        flying.set(rndi(min, max + 1), circleCount);
        break;
    }
    flying.set(flying.x * 6 + circlesOffset.x, flying.y * 6 + circlesOffset.y);
  }
  flyingCount--;
  color(lightColors[flyingColor]);
  if (flyingAngle % 2 === 0) {
    rect(0, flying.y - 1, 99, 2);
  } else {
    rect(flying.x - 1, 0, 2, 99);
  }
  if (flyingCount < 0) {
    flying.addWithAngle((flyingAngle * PI) / 2, difficulty);
    const c = vec(
      round((flying.x - circlesOffset.x) / 6),
      round((flying.y - circlesOffset.y) / 6)
    );
    if (c.isInRect(0, 0, circleCount, circleCount) && circles[c.x][c.y] >= 0) {
      if (flyingColor === 0) {
        flyingColor = circles[c.x][c.y];
        if (flyingColor === 0) {
          flyingColor = rndi(1, 5);
        }
      }
      for (let i = 0; i < 99; i++) {
        c.addWithAngle((flyingAngle * PI) / 2 + PI, 1);
        c.round();
        if (!c.isInRect(0, 0, circleCount, circleCount)) {
          play("explosion");
          color("red");
          text("X", circlesOffset.x + c.x * 6, circlesOffset.y + c.y * 6);
          end();
          break;
        }
        if (circles[c.x][c.y] < 0) {
          play("laser");
          circles[c.x][c.y] = flyingColor;
          flyingCount = -999;
          const [ic, cnt] = checkConnection(c, flyingColor);
          if (cnt >= 4) {
            play("coin");
            let dcc = 0;
            for (let y = 0; y < circleCount; y++) {
              for (let x = 0; x < circleCount; x++) {
                if (ic[x][y]) {
                  color(colors[circles[x][y]]);
                  particle(
                    circlesOffset.x + x * 6,
                    circlesOffset.y + y * 6,
                    4,
                    1
                  );
                  circles[x][y] = -1;
                  dcc++;
                }
              }
            }
            const [icc, _] = checkConnection(vec(cc, cc));
            for (let y = 0; y < circleCount; y++) {
              for (let x = 0; x < circleCount; x++) {
                if (!icc[x][y] && circles[x][y] > 0) {
                  color(lightColors[circles[x][y]]);
                  particle(
                    circlesOffset.x + x * 6,
                    circlesOffset.y + y * 6,
                    4,
                    1
                  );
                  circles[x][y] = -1;
                  dcc++;
                }
              }
            }
            dcc -= 3;
            addScore(dcc * dcc, flying);
          }
          break;
        }
      }
    }
  }
  if (!flying.isInRect(-3, -3, 103, 103)) {
    flyingCount = -999;
  }
  color(colors[flyingColor]);
  char("a", flying);
  if (input.isJustPressed) {
    play("select");
    for (let y = 0; y < circleCount; y++) {
      for (let x = 0; x < circleCount; x++) {
        tmpCircles[x][y] = circles[x][y];
      }
    }
    for (let y = 0; y < circleCount; y++) {
      for (let x = 0; x < circleCount; x++) {
        circles[circleCount - 1 - y][x] = tmpCircles[x][y];
      }
    }
  }
  for (let y = 0; y < circleCount; y++) {
    for (let x = 0; x < circleCount; x++) {
      const c = circles[x][y];
      if (c >= 0) {
        color(colors[c]);
        char("a", circlesOffset.x + x * 6, circlesOffset.y + y * 6);
      }
    }
  }
  function checkConnection(pos, cl = -1) {
    let c = 1;
    const ic = times(circleCount, () => times(circleCount, () => false));
    ic[pos.x][pos.y] = true;
    for (let i = 0; i < 9; i++) {
      const pc = c;
      for (let y = 0; y < circleCount - 1; y++) {
        for (let x = 0; x < circleCount - 1; x++) {
          if (!ic[x][y]) {
            continue;
          }
          if (
            ((cl > 0 && circles[x + 1][y] === cl) ||
              (cl < 0 && circles[x + 1][y] >= 0)) &&
            !ic[x + 1][y]
          ) {
            ic[x + 1][y] = true;
            c++;
          }
          if (
            ((cl > 0 && circles[x][y + 1] === cl) ||
              (cl < 0 && circles[x][y + 1] >= 0)) &&
            !ic[x][y + 1]
          ) {
            ic[x][y + 1] = true;
            c++;
          }
        }
      }
      for (let y = circleCount - 1; y > 0; y--) {
        for (let x = circleCount - 1; x > 0; x--) {
          if (!ic[x][y]) {
            continue;
          }
          if (
            ((cl > 0 && circles[x - 1][y] === cl) ||
              (cl < 0 && circles[x - 1][y] >= 0)) &&
            !ic[x - 1][y]
          ) {
            ic[x - 1][y] = true;
            c++;
          }
          if (
            ((cl > 0 && circles[x][y - 1] === cl) ||
              (cl < 0 && circles[x][y - 1] >= 0)) &&
            !ic[x][y - 1]
          ) {
            ic[x][y - 1] = true;
            c++;
          }
        }
      }
      if (pc === c) {
        break;
      }
    }
    return [ic, c];
  }
  function checkSize() {
    let minX = circleCount - 1,
      maxX = 0;
    let minY = circleCount - 1,
      maxY = 0;
    for (let y = 0; y < circleCount; y++) {
      for (let x = 0; x < circleCount; x++) {
        if (circles[x][y] >= 0) {
          if (minX > x) {
            minX = x;
          }
          if (maxX < x) {
            maxX = x;
          }
          if (minY > y) {
            minY = y;
          }
          if (maxY < y) {
            maxY = y;
          }
        }
      }
    }
    minX = cc - minX + 1;
    maxX = maxX - cc + 1;
    minY = cc - minY + 1;
    maxY = maxY - cc + 1;
    return Math.max(minX, maxX, minY, maxY);
  }
}


<|start_filename|>docs/nsclimb/main.js<|end_filename|>
title = "NS CLIMB";

description = `
[Tap] Reverse
`;

characters = [];

options = {
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 4,
};

/** @type {{pos: Vector, type: "N" | "S" | "", wallIndex: number}[]} */
let magnets;
/**
 * @type {{
 * type: "N" | "S" | "", prevType: "N" | "S", count: number,
 * x: number, vx: number
 * }[]}
 */
let walls;
let pos;
let vel;
let type;

function update() {
  if (!ticks) {
    magnets = times(22, (i) => {
      return {
        pos: vec(i % 2 === 0 ? 20 : 80, (i / 2) * 10 - 5),
        type: i < 10 ? "S" : "",
        wallIndex: i % 2,
      };
    });
    walls = [
      { type: "", prevType: "S", count: rndi(2, 4), x: 20, vx: 0 },
      { type: "", prevType: "S", count: rndi(2, 4), x: 80, vx: 0 },
    ];
    pos = vec(50, 90);
    vel = vec(0, -1);
    type = "N";
  }
  if (input.isJustPressed) {
    type = type === "N" ? "S" : "N";
    play(type === "N" ? "laser" : "select");
  }
  pos.add(vel);
  vel.x *= 0.98;
  vel.y += 0.001 * difficulty;
  let scr = 0;
  if (pos.y < 70) {
    scr = (70 - pos.y) * 0.1;
  }
  if (pos.y > 99) {
    play("explosion");
    end();
  }
  pos.y += scr;
  addScore(scr);
  color(type === "N" ? "red" : "blue");
  box(pos, 7, 7);
  color("white");
  text(type, pos.x - 1, pos.y - 1);
  magnets.forEach((m) => {
    m.pos.y += scr;
    if (m.type !== "") {
      const d = pos.distanceTo(m.pos);
      const a = pos.angleTo(m.pos);
      vel.addWithAngle(a, (difficulty / d / d) * (m.type === type ? -1 : 1));
    }
    color(m.type === "" ? "light_black" : m.type === "N" ? "red" : "blue");
    const c = box(m.pos, 9, 9).isColliding.rect;
    if (c.blue || c.red) {
      play("hit");
      pos.x = m.pos.x + (m.wallIndex === 0 ? 10 : -10);
      if (
        (m.wallIndex === 0 && vel.x < 0) ||
        (m.wallIndex === 1 && vel.x > 0)
      ) {
        vel.x *= -0.7;
      }
    }
    color("white");
    text(m.type, m.pos.x - 1, m.pos.y - 1);
    if (m.pos.y > 105) {
      m.pos.y -= 110;
      const w = walls[m.wallIndex];
      w.x += w.vx;
      m.pos.x = w.x;
      w.vx += rnds(0.1);
      if (m.wallIndex === 0) {
        if ((w.x < 10 && w.vx < 0) || (w.x > 40 && w.vx > 0)) {
          w.vx *= -0.5;
        }
      } else {
        if ((w.x < 60 && w.vx < 0) || (w.x > 90 && w.vx > 0)) {
          w.vx *= -0.5;
        }
      }
      w.count--;
      if (w.count < 0) {
        if (w.type === "") {
          w.type = w.prevType === "N" ? "S" : "N";
          w.prevType = w.type;
          w.count = rndi(3, 9);
        } else {
          w.type = "";
          w.count = rndi(2, 4);
        }
      }
      m.type = w.type;
    }
  });
}


<|start_filename|>docs/slanes/main.js<|end_filename|>
title = "S LANES";

description = `
[Hold]
 Shot & Forward
`;

characters = [
  `
rllbb
lllccb
LlyL b
`,
  `
  r rr
rrRrRR
  grr
  grr
rrRrRR
  r rr
`,
  `
 LLLL
LyyyyL
LyyyyL
LyyyyL
LyyyyL
 LLLL
`,
  `
l Llll
l Llll
`,
];

options = {
  theme: "dark",
  isPlayingBgm: true,
  isReplayEnabled: true,
  seed: 1,
};

/** @type {{pos: Vector, vx: number, laneIndex: number}[]} */
let enemies;
let nextEnemyTicks;
/** @type {{pos: Vector, laneIndex: number}[]} */
let coins;
/** @type {{pos: Vector, vx: number}[]} */
let shots;
/**
 * @type {{
 * pos: Vector, laneIndex: number, targetY: number,
 * laneTicks: number, shotTicks: number
 * }}
 */
let ship;
let laneSpeeds;
let multiplier;
const laneWidth = 20;
const laneCount = 4;
const shipX = 3;

function update() {
  if (!ticks) {
    enemies = [];
    nextEnemyTicks = 0;
    coins = [];
    shots = [];
    ship = {
      pos: vec(shipX, calcY(0)),
      laneIndex: 0,
      targetY: calcY(0),
      laneTicks: 0,
      shotTicks: 0,
    };
    laneSpeeds = times(laneCount, () => 1);
    multiplier = 1;
  }
  nextEnemyTicks--;
  if (nextEnemyTicks < 0) {
    play("select");
    const laneIndex = rndi(laneCount);
    enemies.push({
      pos: vec(103, calcY(laneIndex)),
      vx: -rnd(1, sqrt(difficulty)) * 0.2,
      laneIndex,
    });
    nextEnemyTicks = rnd(60, 90) / difficulty;
  }
  color("black");
  if (input.isPressed) {
    if (input.isJustPressed) {
      ship.pos.x = shipX;
    } else {
      ship.pos.x = clamp(ship.pos.x + sqrt(difficulty) * 0.3, 0, 50);
    }
    ship.pos.y = ship.targetY = calcY(ship.laneIndex);
    ship.shotTicks--;
    if (ship.shotTicks < 0) {
      play("laser");
      shots.push({
        pos: vec(ship.pos.x, wrap(ship.pos.y, 0, 100)),
        vx: difficulty * 2,
      });
      ship.shotTicks = ship.laneTicks = 20 / sqrt(difficulty);
    }
  } else {
    play("hit");
    ship.laneTicks--;
    if (ship.laneTicks < 0) {
      ship.laneIndex = wrap(ship.laneIndex + 1, 0, laneCount);
      ship.targetY = calcY(ship.laneIndex) + (ship.laneIndex === 0 ? 100 : 0);
      ship.pos.y = wrap(ship.pos.y, 0, 100);
      ship.laneTicks = 20 / sqrt(difficulty);
    }
    ship.pos.x += (shipX - ship.pos.x) * 0.3;
    ship.pos.y += (ship.targetY - ship.pos.y) * 0.3;
  }
  char("a", ship.pos.x, wrap(ship.pos.y, 0, 100));
  color("light_purple");
  times(laneCount, (i) => {
    laneSpeeds[i] += (0.9 - laneSpeeds[i]) * 0.02;
    text(times(ceil(laneSpeeds[i]), () => "<").join(""), 9, calcY(i));
  });
  color("black");
  const cvx = -difficulty * 0.2;
  remove(coins, (c) => {
    c.pos.x += cvx;
    if (char("c", c.pos).isColliding.char.a) {
      play("coin");
      addScore(multiplier, c.pos);
      multiplier++;
      return true;
    }
    if (c.pos.x < -3) {
      play("explosion");
      laneSpeeds[c.laneIndex]++;
      if (multiplier > 1) {
        multiplier--;
      }
      return true;
    }
  });
  shots.forEach((s) => {
    s.pos.x += s.vx;
    char("d", s.pos);
  });
  color("black");
  remove(enemies, (e) => {
    e.pos.x += e.vx * laneSpeeds[e.laneIndex];
    if (char("b", e.pos).isColliding.char.d) {
      play("powerUp");
      particle(e.pos);
      coins.push({ pos: e.pos, laneIndex: e.laneIndex });
      return true;
    }
    if (e.pos.x < 9) {
      play("lucky");
      color("red");
      text("X", 3, e.pos.y);
      end();
      color("black");
    }
  });
  color("transparent");
  remove(shots, (s) => {
    return s.pos.x > 103 || char("d", s.pos).isColliding.char.b;
  });

  function calcY(i) {
    return i * laneWidth + laneWidth / 2 + 12;
  }
}
