maze-generation
Version:
A package to generate mazes using the depth first or hunt and kill algorithm. Mazes can be generated with seed values for reproducibility
323 lines (322 loc) • 15.4 kB
JavaScript
import mazegeneration from "../../index";
/* eslint-env jest */
describe("Maze created with the correct dimensions", () => {
it.each([
[1, 1],
[3, 3],
[50, 100],
[100, 50],
[2999, 2999],
[3000, 3000],
])("is created correctly when dimensions are %i by %i", (width, height) => {
const maze = mazegeneration({ width: width, height: height });
const expectedHeight = maze.cells.length;
const expectedWidth = maze.cells[0].length;
expect(expectedWidth).toBe(width);
expect(expectedHeight).toBe(height);
});
});
describe("Algorithms", () => {
const testOptions = {
width: 10,
height: 10,
};
describe("Default (DEPTHFIRST)", () => {
it("generates the same maze if the same numerical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
expect(maze1).toEqual(maze2);
});
it("generates the same maze if the same alphabetical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
expect(maze1).toEqual(maze2);
});
it("generates a different maze if a different numerical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 11234 }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
expect(maze1).not.toEqual(maze2);
});
it("generates a different maze if a different alphabetical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "testseed" }));
expect(maze1).not.toEqual(maze2);
});
});
});
describe("Seed generation", () => {
let testOptions = {
width: 10,
height: 10,
algorithm: "",
};
describe("DEPTHFIRST", () => {
testOptions = Object.assign(Object.assign({}, testOptions), { algorithm: "DEPTHFIRST" });
it("generates the same maze if the same numerical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
expect(maze1).toEqual(maze2);
});
it("generates the same maze if the same alphabetical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
expect(maze1).toEqual(maze2);
});
it("generates a different maze if a different numerical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 11234 }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
expect(maze1).not.toEqual(maze2);
});
it("generates a different maze if a different alphabetical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "testseed" }));
expect(maze1).not.toEqual(maze2);
});
});
describe("Default (DEPTHFIRST)", () => {
it("generates the same maze if the same numerical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
expect(maze1).toEqual(maze2);
});
it("generates the same maze if the same alphabetical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
expect(maze1).toEqual(maze2);
});
it("generates a different maze if a different numerical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 11234 }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
expect(maze1).not.toEqual(maze2);
});
it("generates a different maze if a different alphabetical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "testseed" }));
expect(maze1).not.toEqual(maze2);
});
});
describe("HUNTANDKILL", () => {
testOptions = Object.assign(Object.assign({}, testOptions), { algorithm: "HUNTANDKILL" });
it("generates the same maze if the same numerical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
expect(maze1).toEqual(maze2);
});
it("generates the same maze if the same alphabetical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
expect(maze1).toEqual(maze2);
});
it("generates a different maze if a different numerical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 11234 }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: 12345 }));
expect(maze1).not.toEqual(maze2);
});
it("generates a different maze if a different alphabetical seed is provided", () => {
const maze1 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "test" }));
const maze2 = mazegeneration(Object.assign(Object.assign({}, testOptions), { seed: "testseed" }));
expect(maze1).not.toEqual(maze2);
});
});
});
describe("Error handling", () => {
describe("min / max Dimensions", () => {
describe("Width and Height must be above 0", () => {
it.each([
[0, 0],
[3, 0],
[0, 3],
])("throws an error if width is %i and height is %i", (width, height) => {
expect(() => {
const options = { width: width, height: height };
mazegeneration(options);
}).toThrowError("Width and height must be greater than 0");
});
});
describe("Width and Height must be below 3000", () => {
it.each([
[3001, 3001],
[3001, 3],
[3, 3001],
])("throws an error if width is %i and height is %i", (width, height) => {
expect(() => {
const options = { width: width, height: height };
mazegeneration(options);
}).toThrowError("Height and width must be a maximum of 3000");
});
});
});
describe("no required parameters", () => {
it("throws an error if an empty object is passed as parameter", () => {
expect(() => {
const options = {};
mazegeneration(options);
}).toThrowError("An object with the following parameters is required to generate a maze:\n{ height, width, seed (optional), algorithm (optional) }");
});
it.each([
[{ width: 3 }],
[{ width: 3, seed: 1234, algorithm: "HUNTANDKILL" }],
])("throws an error if an object without the height parameter is passed", () => {
expect(() => {
const options = { width: 3 };
mazegeneration(options);
}).toThrowError("An object with the following parameters is required to generate a maze:\n{ height, width, seed (optional), algorithm (optional) }");
});
it.each([
[{ height: 3 }],
[{ height: 3, seed: 1234, algorithm: "HUNTANDKILL" }],
])("throws an error if an object without the width parameter is passed", () => {
expect(() => {
const options = { width: 3 };
mazegeneration(options);
}).toThrowError("An object with the following parameters is required to generate a maze:\n{ height, width, seed (optional), algorithm (optional) }");
});
it("throws an error if an object is passed without both dimension parameters", () => {
expect(() => {
const options = { seed: 1234, algorithm: "HUNTANDKILL" };
mazegeneration(options);
}).toThrowError("An object with the following parameters is required to generate a maze:\n{ height, width, seed (optional), algorithm (optional) }");
});
});
it("Invalid algorithm", () => {
const options = {
width: 3,
height: 3,
seed: 12345,
algorithm: "INVALIDALGORITHM",
};
expect(() => {
mazegeneration(options);
}).toThrowError(`${options.algorithm} is an Invalid Maze Generation Algorithm`);
});
describe("height and width types", () => {
describe("are passed as strings", () => {
it.each([
["3", "3"],
["3", 3],
[3, "3"],
])("throws an error if width is %s and height is %s", (width, height) => {
const options = { width: width, height: height };
expect(() => {
mazegeneration(options);
}).toThrowError("Width and height must be numbers");
});
});
describe("are passed as boolean", () => {
it.each([
[3, false],
[3, true],
[false, false],
[false, true],
[false, 3],
[true, 3],
])("throws an error if width is %o and height is %o", (width, height) => {
const options = { width: width, height: height };
expect(() => {
mazegeneration(options);
}).toThrowError("Width and height must be numbers");
});
});
describe("objects", () => {
it.each([
[{ width: 3 }, { height: 3 }],
[3, { height: 3 }],
[{ width: 3 }, 3],
])("throws an error if width is %o and height is %o", (width, height) => {
const options = { width: width, height: height };
expect(() => {
mazegeneration(options);
}).toThrowError("Width and height must be numbers");
});
});
});
});
describe("Can solve a maze and display the path", () => {
const testOptions = {
width: 10,
height: 10,
seed: "testseed",
};
let testMaze;
let generatedSolution;
describe("DEPTHFIRST", () => {
beforeEach(() => {
testMaze = mazegeneration(Object.assign(Object.assign({}, testOptions), { algorithm: "DEPTHFIRST" }));
});
describe("cannot find path", () => {
beforeEach(() => {
testMaze.cells[8][9].walls = {
left: true,
right: true,
up: true,
down: true,
};
testMaze.cells[9][8].walls = {
left: true,
right: true,
up: true,
down: true,
};
testMaze.cells[9][9].walls = {
left: true,
right: true,
up: true,
down: true,
};
generatedSolution = testMaze.generateSolution({ row: 0, column: 0 }, { row: 9, column: 9 });
});
it("returns path as a nempty array if no path found", () => {
expect(generatedSolution.path).toBeInstanceOf(Array);
expect(generatedSolution.path.length).toEqual(0);
});
it("shows an empty string if using toString on an empty path", () => {
const expectedString = "";
expect(generatedSolution.toString()).toEqual(expectedString);
});
it("shows an empty array if using toJSON on an empty path", () => {
const jsonPath = generatedSolution.toJSON();
expect(jsonPath).toEqual([]);
});
});
});
describe.each(["DEPTHFIRST", "HUNTANDKILL"])("Path finding with algorithm: %s", (algorithm) => {
beforeEach(() => {
testMaze = mazegeneration(Object.assign(Object.assign({}, testOptions), { algorithm }));
});
describe("cannot find path", () => {
beforeEach(() => {
testMaze.cells[8][9].walls = {
left: true,
right: true,
up: true,
down: true,
};
testMaze.cells[9][8].walls = {
left: true,
right: true,
up: true,
down: true,
};
testMaze.cells[9][9].walls = {
left: true,
right: true,
up: true,
down: true,
};
generatedSolution = testMaze.generateSolution({ row: 0, column: 0 }, { row: 9, column: 9 });
});
it("returns empty array if impassible returns path if no path found", () => {
expect(generatedSolution.path).toBeInstanceOf(Array);
expect(generatedSolution.path.length).toEqual(0);
});
it("shows an empty string if using toString on an empty path", () => {
const expectedString = "";
expect(generatedSolution.toString()).toEqual(expectedString);
});
it("shows an empty array if using toJSON on an empty path", () => {
const expectedJson = [];
expect(generatedSolution.toJSON()).toEqual(expectedJson);
});
});
});
});