peer-kit / test / valley-blocks-gym.test.mjs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// Valley-blocks gym — piece-synchronous self-play over the real referee.
import test from "node:test";
import assert from "node:assert/strict";
import { ValleyBlocksGym } from "../dist/gym/valley-blocks.js";

function playEpisode(gym, pick = () => null) {
  let views = gym.reset();
  const log = [];
  for (let step = 0; step < 500; step++) {
    const res = gym.step(views.map((v, seat) => pick(v, seat)));
    log.push(res.rewards.join(","));
    if (res.done) return { res, log, steps: step + 1 };
    views = res.views;
  }
  return { res: null, log, steps: 500 };
}

test("two stock seats race to a decided verdict", () => {
  const gym = new ValleyBlocksGym({ players: 2, seed: 11, mode: "race-survival", target: 1500 });
  const { res, steps } = playEpisode(gym);
  assert.ok(res, "episode ended");
  assert.ok(res.winner === 0 || res.winner === 1);
  assert.ok(["target", "survivor", "score"].includes(res.reason));
  assert.ok(steps < 300, `Dellacherie reaches 1500 briskly (took ${steps})`);
  const winnerView = res.views[res.winner];
  if (res.reason === "target") assert.ok(winnerView.score >= 1500);
});

test("same gym seed → an identical episode (rewards and verdict)", () => {
  const a = playEpisode(new ValleyBlocksGym({ players: 2, seed: 77, target: 1200 }));
  const b = playEpisode(new ValleyBlocksGym({ players: 2, seed: 77, target: 1200 }));
  assert.deepEqual(a.log, b.log);
  assert.equal(a.res.winner, b.res.winner);
  assert.equal(a.res.reason, b.res.reason);
});

test("views carry the live-client fields and both seats see the same pieces", () => {
  const gym = new ValleyBlocksGym({ players: 3, seed: 5 });
  const views = gym.reset();
  assert.equal(views.length, 3);
  for (const [seat, v] of views.entries()) {
    assert.equal(v.seat, seat);
    assert.equal(v.over, false);
    assert.equal(v.mode, "race-survival");
    assert.equal(v.target, 5000);
    assert.equal(v.rivals.length, 2);
    assert.ok(v.candidates.length > 0);
    assert.ok(v.candidates.some((c) => c.useHold), "hold branch offered");
    assert.equal(v.piece, views[0].piece, "shared seed deals identical pieces");
  }
});

test("the placement cap forces a highest-score verdict", () => {
  const gym = new ValleyBlocksGym({ players: 2, seed: 3, mode: "race", target: 10_000_000, maxPlacements: 10 });
  let views = gym.reset();
  let res = null;
  for (let i = 0; i < 20; i++) {
    res = gym.step(views.map(() => null));
    if (res.done) break;
    views = res.views;
  }
  assert.ok(res.done, "capped episode ends");
  assert.equal(res.reason, "score");
});

test("rewards: score deltas flow every step, the winner gets the bonus", () => {
  const gym = new ValleyBlocksGym({ players: 2, seed: 21, target: 800 });
  let views = gym.reset();
  let sawPositive = false;
  for (let i = 0; i < 400; i++) {
    const res = gym.step(views.map(() => null));
    for (const r of res.rewards) if (r > 0 && r < 1) sawPositive = true;
    if (res.done) {
      assert.ok(res.rewards[res.winner] >= 1, "win bonus lands on the winner's last step");
      break;
    }
    views = res.views;
  }
  assert.ok(sawPositive, "hard drops and clears show up as shaped reward");
});

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/peer-kit.git