177 lines
5.5 KiB
TypeScript
177 lines
5.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
activeHypothesis,
|
|
beginHypothesis,
|
|
createGameplayHistory,
|
|
createSavepoint,
|
|
deleteSavepoint,
|
|
describeGameplayChange,
|
|
finishHypothesis,
|
|
gameplayMoment,
|
|
recordGameplayMoment,
|
|
restoreSavepoint,
|
|
sessionFromGameplayState,
|
|
} from "../../src/state/playHistory";
|
|
import {
|
|
createAidMemoire,
|
|
enterAidMemoireCell,
|
|
} from "../../src/state/aidMemoire";
|
|
import { createSession } from "../../src/state/session";
|
|
|
|
describe("gameplay history", () => {
|
|
it("records immutable states with useful move labels", () => {
|
|
const initial = createSession([0, 0, 0, 0]);
|
|
const changed = { ...initial, values: [0, 3, 0, 0] };
|
|
const label = describeGameplayChange(initial, changed, 2);
|
|
const history = recordGameplayMoment(
|
|
createGameplayHistory(initial),
|
|
changed,
|
|
label,
|
|
);
|
|
|
|
expect(label).toBe("Set r1c2 to 3");
|
|
expect(history.moments.map((moment) => moment.label)).toEqual([
|
|
"Puzzle opened",
|
|
"Set r1c2 to 3",
|
|
]);
|
|
changed.values[1] = 4;
|
|
expect(history.moments[1]!.state.values[1]).toBe(3);
|
|
});
|
|
|
|
it("creates, restores and deletes full named savepoints", () => {
|
|
const session = createSession([0, 0, 0, 0]);
|
|
session.values[1] = 2;
|
|
session.centerMarks[2] = 5;
|
|
session.colors[3] = 4;
|
|
session.elapsedSeconds = 75;
|
|
let history = createSavepoint(
|
|
createGameplayHistory(session),
|
|
session,
|
|
"Before chain",
|
|
);
|
|
|
|
session.values[1] = 4;
|
|
session.centerMarks[2] = 0;
|
|
const transition = restoreSavepoint(history, history.savepoints[0]!.id);
|
|
const restored = sessionFromGameplayState(transition.state);
|
|
expect(restored.values[1]).toBe(2);
|
|
expect(restored.centerMarks[2]).toBe(5);
|
|
expect(restored.colors[3]).toBe(4);
|
|
expect(restored.elapsedSeconds).toBe(75);
|
|
expect(transition.history.moments.at(-1)?.label).toBe(
|
|
"Restored “Before chain”",
|
|
);
|
|
|
|
history = deleteSavepoint(
|
|
transition.history,
|
|
transition.history.savepoints[0]!.id,
|
|
);
|
|
expect(history.savepoints).toHaveLength(0);
|
|
});
|
|
|
|
it("captures aid-mémoire state in moments and savepoints", () => {
|
|
const session = createSession([0, 0, 0, 0]);
|
|
let aidMemoire = createAidMemoire(2, {
|
|
enabled: true,
|
|
cellCount: 2,
|
|
});
|
|
aidMemoire = enterAidMemoireCell(aidMemoire, 0, "value", 1, 2);
|
|
const history = createSavepoint(
|
|
createGameplayHistory(session, aidMemoire),
|
|
session,
|
|
"Scratch one",
|
|
aidMemoire,
|
|
);
|
|
|
|
aidMemoire = enterAidMemoireCell(aidMemoire, 0, "value", 2, 2);
|
|
const restored = restoreSavepoint(history, history.savepoints[0]!.id);
|
|
expect(restored.state.aidMemoire?.cells[0]?.value).toBe(1);
|
|
expect(history.moments[0]?.state.aidMemoire?.cells[0]?.value).toBe(1);
|
|
expect(aidMemoire.cells[0]?.value).toBe(2);
|
|
});
|
|
|
|
it("keeps discarded hypotheses available while restoring their exact base", () => {
|
|
const session = createSession([0, 0, 0, 0]);
|
|
session.values[0] = 1;
|
|
let transition = beginHypothesis(
|
|
createGameplayHistory(session),
|
|
session,
|
|
"Try a 2",
|
|
);
|
|
expect(activeHypothesis(transition.history)?.name).toBe("Try a 2");
|
|
|
|
const branchSession = sessionFromGameplayState(transition.state);
|
|
branchSession.values[1] = 2;
|
|
transition = {
|
|
history: recordGameplayMoment(
|
|
transition.history,
|
|
branchSession,
|
|
"Set r1c2 to 2",
|
|
),
|
|
state: transition.state,
|
|
};
|
|
const result = finishHypothesis(
|
|
transition.history,
|
|
branchSession,
|
|
"discard",
|
|
);
|
|
|
|
expect(result.state.values).toEqual([1, 0, 0, 0]);
|
|
expect(result.history.branches[0]?.status).toBe("discarded");
|
|
expect(
|
|
result.history.moments.some(
|
|
(moment) =>
|
|
moment.branchId === result.history.branches[0]!.id &&
|
|
moment.state.values[1] === 2,
|
|
),
|
|
).toBe(true);
|
|
expect(result.history.moments.at(-1)?.label).toBe(
|
|
"Discarded hypothesis “Try a 2”",
|
|
);
|
|
});
|
|
|
|
it("can branch from a replayed moment and merge accepted changes", () => {
|
|
const initial = createSession([0, 0, 0, 0]);
|
|
const first = { ...initial, values: [1, 0, 0, 0] };
|
|
const second = { ...initial, values: [1, 2, 0, 0] };
|
|
let history = recordGameplayMoment(
|
|
createGameplayHistory(initial),
|
|
first,
|
|
"First move",
|
|
);
|
|
const replayId = history.currentMomentId;
|
|
history = recordGameplayMoment(history, second, "Second move");
|
|
|
|
let transition = beginHypothesis(history, second, "Alternative", replayId);
|
|
expect(transition.state.values).toEqual([1, 0, 0, 0]);
|
|
const alternative = sessionFromGameplayState(transition.state);
|
|
alternative.values[2] = 3;
|
|
transition = {
|
|
history: recordGameplayMoment(
|
|
transition.history,
|
|
alternative,
|
|
"Alternative move",
|
|
),
|
|
state: transition.state,
|
|
};
|
|
const kept = finishHypothesis(transition.history, alternative, "keep");
|
|
|
|
expect(kept.state.values).toEqual([1, 0, 3, 0]);
|
|
expect(kept.history.branches.at(-1)?.status).toBe("kept");
|
|
expect(
|
|
gameplayMoment(kept.history, kept.history.currentMomentId)?.label,
|
|
).toBe("Kept hypothesis “Alternative”");
|
|
});
|
|
|
|
it("rejects blank checkpoint and hypothesis names", () => {
|
|
const session = createSession([0, 0, 0, 0]);
|
|
const history = createGameplayHistory(session);
|
|
expect(() => createSavepoint(history, session, " ")).toThrow(
|
|
"Savepoint name cannot be empty",
|
|
);
|
|
expect(() => beginHypothesis(history, session, " ")).toThrow(
|
|
"Hypothesis name cannot be empty",
|
|
);
|
|
});
|
|
});
|