Skip to content

Commit

Permalink
Merge pull request #61 from frostburn/individual-seeds
Browse files Browse the repository at this point in the history
Make seeds player-specific
  • Loading branch information
frostburn authored Nov 8, 2023
2 parents 9fae1de + 7888592 commit 624a1f5
Show file tree
Hide file tree
Showing 8 changed files with 365 additions and 132 deletions.
65 changes: 44 additions & 21 deletions src/__tests__/ai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MultiplayerGame,
PASS,
SimpleGame,
randomBag,
randomColorSelection,
} from '../game';
import {effectiveLockout, flexDropletStrategy1} from '../ai';
Expand Down Expand Up @@ -115,17 +116,29 @@ test('Ineffective lockout (no bag)', () => {
expect(heuristic).toBe(0);
});

// Skipped due to simulating a whole game with non-trivial AI being a bit heavy
test.skip('Server/client pausing game simulation', () => {
test('Server/client pausing game simulation', () => {
const maxConsecutiveRerolls = 10;
const gameSeed = randomSeed();
const screenSeed = randomSeed();
const gameSeeds = [randomSeed(), randomSeed()];
const screenSeeds = [randomSeed(), randomSeed()];
const colorSelection = randomColorSelection();
const colorSelections = [colorSelection, colorSelection];
const main = new MultiplayerGame(gameSeed, screenSeed, colorSelections);
const initialBag = randomBag(colorSelection);
const initialBags = [initialBag, initialBag];
const main = new MultiplayerGame(
gameSeeds,
screenSeeds,
colorSelections,
initialBags
);

// In practice this would be two mirrors for each client
const mirror = new MultiplayerGame(null, screenSeed, colorSelections);
const knownBags = main.initialBags;
const mirror = new MultiplayerGame(
null,
screenSeeds,
colorSelections,
knownBags
);
for (let i = 0; i < mirror.games.length; ++i) {
// Send initial bag and prompt moves with next pieces
mirror.games[i].bag = main.games[i].initialBag.concat(
Expand Down Expand Up @@ -209,25 +222,35 @@ test.skip('Server/client pausing game simulation', () => {
}
});

// Skipped due to simulating a whole game with non-trivial AI being a bit heavy
// At least it's not running in wall clock time...
test.skip('Server/client realtime game simulation', () => {
test('Server/client realtime game simulation', () => {
const maxConsecutiveRerolls = 10;
const gameSeed = randomSeed();
const screenSeed = randomSeed();
const gameSeeds = [randomSeed(), randomSeed()];
const screenSeeds = [randomSeed(), randomSeed()];
const colorSelection = randomColorSelection();
const colorSelections = [colorSelection, colorSelection];
const origin = new MultiplayerGame(gameSeed, screenSeed, colorSelections);
const initialBag = randomBag(colorSelection);
const initialBags = [initialBag, initialBag];
const origin = new MultiplayerGame(
gameSeeds,
screenSeeds,
colorSelections,
initialBags
);

// Server
const main = new TimeWarpingGame(origin);

const mirrorOrigin = new MultiplayerGame(null, screenSeed, colorSelections);
const initialBags = origin.initialBags;
const knownBags = origin.initialBags;
const mirrorOrigin = new MultiplayerGame(
null,
screenSeeds,
colorSelections,
knownBags
);
// Two dueling clients
const mirrors = [
new TimeWarpingMirror(mirrorOrigin, initialBags),
new TimeWarpingMirror(mirrorOrigin, initialBags),
new TimeWarpingMirror(mirrorOrigin),
new TimeWarpingMirror(mirrorOrigin),
];

// Client-side
Expand Down Expand Up @@ -263,7 +286,7 @@ test.skip('Server/client realtime game simulation', () => {
} else {
const {x1, y1, orientation} = MOVES[strategy.move];
const move = game.play(0, x1, y1, orientation, true);
console.log('Adding', move);
// console.log('Adding', move);
const rejectedMoves = main.addMove(move);
mirrors[0].addMove(move);
mirrors[1].addMove(move);
Expand All @@ -286,7 +309,7 @@ test.skip('Server/client realtime game simulation', () => {
} else {
const {x1, y1, orientation} = MOVES[strategy.move];
const move = game.play(1, x1, y1, orientation, Math.random() > 0.2);
console.log('Adding', move);
// console.log('Adding', move);
const rejectedMoves = main.addMove(move);
mirrors[0].addMove(move);
mirrors[1].addMove(move);
Expand All @@ -311,7 +334,7 @@ test.skip('Server/client realtime game simulation', () => {
serverTime++;
const pieces = main.revealPieces(serverTime);
for (const piece of pieces) {
console.log('Revealing', piece);
// console.log('Revealing', piece);
if (piece.player === 0) {
botTime = piece.time;
} else {
Expand Down Expand Up @@ -339,12 +362,12 @@ test.skip('Server/client realtime game simulation', () => {
}

const game = main.warp(serverTime);
game.log();
// game.log();
const mirrorGames = mirrors.map(m => m.warp(serverTime)[0]);

for (const mirrorGame of mirrorGames) {
expect(mirrorGame).not.toBeNull();
mirrorGame!.log();
// mirrorGame!.log();
}

for (let i = 0; i < game.games.length; ++i) {
Expand Down
20 changes: 12 additions & 8 deletions src/__tests__/archive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,30 @@ import {JKISS32} from '../jkiss';
import {Replay} from '../replay';

export function fixedRandomGame() {
const gameSeed = 7;
const gameSeeds = [7, 7];
const colorSelection = [1, 2, 3, 4];
const colorSelections = [colorSelection, colorSelection];
const screenSeed = 11;
const initialBags = [[], []];
const screenSeeds = [11, 11];
const targetPoints = [70, 70];
const marginFrames = DEFAULT_MARGIN_FRAMES;
const mercyFrames = DEFAULT_MERCY_FRAMES;
const game = new MultiplayerGame(
gameSeed,
screenSeed,
gameSeeds,
screenSeeds,
colorSelections,
initialBags,
targetPoints,
marginFrames,
mercyFrames
);
const rng = new JKISS32(8);

const replay: Replay = {
gameSeed,
screenSeed,
gameSeeds,
screenSeeds,
colorSelections,
initialBags,
targetPoints,
marginFrames,
mercyFrames,
Expand Down Expand Up @@ -69,12 +72,13 @@ export function fixedRandomGame() {
}

export const LUMI_VS_FLEX2: Replay = {
gameSeed: 3864657304,
screenSeed: 2580717322,
gameSeeds: [3864657304, 3864657304],
screenSeeds: [2580717322, 2580717322],
colorSelections: [
[3, 1, 0, 2],
[3, 1, 0, 2],
],
initialBags: [[], []],
targetPoints: [70, 70],
marginFrames: DEFAULT_MARGIN_FRAMES,
mercyFrames: Infinity,
Expand Down
80 changes: 63 additions & 17 deletions src/__tests__/game.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import {
DEFAULT_TARGET_POINTS,
MOVES,
MultiplayerGame,
OnePlayerGame,
SimpleGame,
SinglePlayerGame,
randomBag,
randomColorSelection,
} from '../game';
import {JKISS32, randomSeed} from '../jkiss';
Expand Down Expand Up @@ -72,13 +72,13 @@ test('No pending flash', () => {

test('Garbage schedule', () => {
// Create a deterministic game.
const game = new MultiplayerGame(0);
const game = new MultiplayerGame([0, 0]);
// Create a deterministic player that is somewhat successful.
const jkiss = new JKISS32(7);
const jkiss = new JKISS32(1);
// Create a dummy opponent.
const dummy = new JKISS32(420);

for (let i = 0; i < 1950; ++i) {
for (let i = 0; i < 1500; ++i) {
if (!game.games[0].busy) {
const {x1, y1, orientation} = MOVES[jkiss.step() % MOVES.length];
game.play(0, x1, y1, orientation);
Expand All @@ -102,12 +102,12 @@ test('Garbage schedule', () => {
game.tick();
}

expect(puyoCount(game.games[1].screen.grid[GARBAGE])).toBe(3);
expect(puyoCount(game.games[1].screen.grid[GARBAGE])).toBe(11);
});

test('Garbage offset in a fixed symmetric game', () => {
// Create a random game.
const game = new MultiplayerGame(592624221);
const game = new MultiplayerGame([592624221, 592624221]);
// Create players with identical strategies.
const players = [new JKISS32(3848740175), new JKISS32(3848740175)];

Expand All @@ -126,7 +126,7 @@ test('Garbage offset in a fixed symmetric game', () => {
test('Garbage offset in a random symmetric game', () => {
// Create a random game.
const gameSeed = randomSeed();
const game = new MultiplayerGame(gameSeed);
const game = new MultiplayerGame([gameSeed, gameSeed]);
// Create players with identical strategies.
const playerSeed = randomSeed();
const players = [new JKISS32(playerSeed), new JKISS32(playerSeed)];
Expand Down Expand Up @@ -186,31 +186,35 @@ test('Simple game pending garbage offsetting', () => {
});

test('Roof play', () => {
const game = new OnePlayerGame();
const game = new SinglePlayerGame();
// Not recommended to play on the garbage insert line but kicks should still apply.
game.play(0, 1, 0);
expect(puyoCount(game.screen.mask)).toBe(2);
});

test('Mirror driving', () => {
const mainSeed = randomSeed();
const mainSeeds = [randomSeed(), randomSeed()];
const colorSelection = randomColorSelection();
const colorSelections = [colorSelection, colorSelection];
const screenSeed = randomSeed();
const screenSeeds = [randomSeed(), randomSeed()];
const initialBag = randomBag(colorSelection);
const initialBags = [initialBag, initialBag];
const targetPoints = [70, 70];
const marginTime = 5000;
const main = new MultiplayerGame(
mainSeed,
screenSeed,
mainSeeds,
screenSeeds,
colorSelections,
initialBags,
targetPoints,
marginTime
);

const mirror = new MultiplayerGame(
null,
screenSeed,
screenSeeds,
colorSelections,
[[], []],
targetPoints,
marginTime
);
Expand Down Expand Up @@ -287,7 +291,7 @@ test('Permanent lockout', () => {
});

test('To simple game JSON', () => {
const game = new MultiplayerGame(0);
const game = new MultiplayerGame([0, 0]);
game.play(0, 1, 2, 3, true);
game.play(1, 2, 3, 0, true);
while (game.tick()[0].busy);
Expand Down Expand Up @@ -378,7 +382,7 @@ test('Move count reduction (rerolls)', () => {
});

test('Null end', () => {
const game = new MultiplayerGame(0);
const game = new MultiplayerGame([0, 0]);

while (true) {
if (!game.games[0].busy) {
Expand All @@ -397,7 +401,7 @@ test('Null end', () => {
});

test('AFK end', () => {
const game = new MultiplayerGame(1);
const game = new MultiplayerGame([1, 1]);

while (!game.tick()[0].lockedOut);
expect(game.age).toBe(12502);
Expand All @@ -406,7 +410,13 @@ test('AFK end', () => {
test('Handicap', () => {
const colorSelection = [RED, GREEN, YELLOW, BLUE];
const colorSelections = [colorSelection, colorSelection];
const game = new MultiplayerGame(11, 17, colorSelections, [1, 70]);
const game = new MultiplayerGame(
[11, 11],
[17, 17],
colorSelections,
[[], []],
[1, 70]
);
game.play(0, 0, 0, 0, true);
while (game.tick()[0].busy);
game.play(0, 1, 0, 0, true);
Expand Down Expand Up @@ -459,3 +469,39 @@ test('No mercy flashes', () => {
game.tick();
}
});

test('No mirror cheese', () => {
// Create a deterministic game with anti-cheese seeds.
const game = new MultiplayerGame([0, 1]);
// Create a deterministic player that is somewhat successful.
const jkiss = new JKISS32(7);

let move: (typeof MOVES)[number] | null = null;

for (let i = 0; i < 1950; ++i) {
// Play using cheesy mirror strategy.
if (move !== null) {
expect(!game.games[1].busy);
game.play(1, move.x1, move.y1, move.orientation);
move = null;
}
if (!game.games[0].busy) {
// The cheese works but only up to a limit.
if (game.age < 426) {
for (let i = 0; i < game.games[0].screen.grid.length; ++i) {
expect(
puyosEqual(
game.games[0].screen.grid[i],
game.games[1].screen.grid[i]
)
).toBeTrue();
}
}
move = MOVES[jkiss.step() % MOVES.length];
game.play(0, move.x1, move.y1, move.orientation);
}
game.tick();
}

expect(game.games[0].score).not.toBe(game.games[1].score);
});
Loading

0 comments on commit 624a1f5

Please sign in to comment.