diff --git a/README.MD b/README.MD index 3110d0c..4480c21 100644 --- a/README.MD +++ b/README.MD @@ -20,10 +20,17 @@ ```bash tsc ``` +Executing this in the application's root folder will use all settings from `tsconfig.json`. ## Run Run the application. ```bash node dist/app.js +``` + +## Execute tests + +```bash +npm test ``` \ No newline at end of file diff --git a/README.MD b/README.MD index 3110d0c..4480c21 100644 --- a/README.MD +++ b/README.MD @@ -20,10 +20,17 @@ ```bash tsc ``` +Executing this in the application's root folder will use all settings from `tsconfig.json`. ## Run Run the application. ```bash node dist/app.js +``` + +## Execute tests + +```bash +npm test ``` \ No newline at end of file diff --git a/src/MazeMap.spec.ts b/src/MazeMap.spec.ts new file mode 100644 index 0000000..f54557a --- /dev/null +++ b/src/MazeMap.spec.ts @@ -0,0 +1,190 @@ +import {expect} from "chai"; +import { Direction, MazeMap, MazeTile, MazeValidationError, TileValidationError } from "./MazeMap"; +// import { describe, it } from "mocha"; + +describe("Scenario: validate a single tile", function(){ + describe("When it is valid", function(){ + const tile = new MazeTile("id"); + tile.paths.push(Direction.right); + const result = tile.validate(); + it("then it returns an empty array.", function(){ + expect(result).to.not.be.null; + expect(Array.isArray(result)).to.be.true; + expect(result.length).to.equal(0); + }); + }); + + describe("When id is null", function(){ + const tile = new MazeTile(null); + const result = tile.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(TileValidationError.InvalidID); + }); + }); + + describe("When id is empty", function(){ + const tile = new MazeTile(""); + const result = tile.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(TileValidationError.InvalidID); + }); + }); + + describe("When no path way is given", function(){ + const tile = new MazeTile("id"); + const result = tile.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(TileValidationError.NoPathways); + }); + }); + + describe("When the only path is \"none\"", function(){ + const tile = new MazeTile("id"); + tile.paths.push(Direction.none); + const result = tile.validate(); + it("then it is valid", function(){ + expect(result.length).to.equal(0); + }); + }); +}); + +function createMinimalisticMaze(): MazeMap +{ + const maze = new MazeMap("id"); + let tile = new MazeTile("1"); + tile.paths.push(Direction.right); + maze.layout.push([tile]); + tile = new MazeTile("2"); + tile.paths.push(Direction.left); + maze.layout[0].push(tile); + return maze; +} + +describe("Scenario: validate a whole maze", function(){ + describe("When it is valid", function(){ + const maze = createMinimalisticMaze(); + const result = maze.validate(); + it("then it returns an empty array.", function(){ + expect(result).to.not.be.null; + expect(Array.isArray(result)).to.be.true; + expect(result.length).to.equal(0); + }); + }); + + describe("When the id is null", function(){ + const maze = createMinimalisticMaze(); + maze.id = null; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.InvalidID); + }); + }); + + describe("When the id is empty", function(){ + const maze = createMinimalisticMaze(); + maze.id = ""; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.InvalidID); + }); + }); + + describe("When one tile is invalid", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].id = null; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.InvalidTiles); + }); + }); + + describe("When it is not rectangular shaped", function(){ + const maze = createMinimalisticMaze(); + const tile = new MazeTile("3"); + tile.paths.push(Direction.none); + maze.layout.push([tile]); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.WrongShape); + }); + }); + + describe("When it has a null tile", function(){ + const maze = createMinimalisticMaze(); + const tile = new MazeTile("3"); + tile.paths.push(Direction.none); + maze.layout.push([tile, null]); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.NoHolesAllowed); + }); + }); + + describe("When it has a null row at the end", function(){ + const maze = createMinimalisticMaze(); + maze.layout.push(null); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.NoHolesAllowed); + }); + }); + + describe("When a path leaves the map on the top side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].paths.push(Direction.top); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path leaves the map on the left side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].paths.push(Direction.left); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path leaves the map on the bottom side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].paths.push(Direction.bottom); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path leaves the map on the right side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][1].paths.push(Direction.right); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path does not have an opposite on the other side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][1].paths[0] = Direction.none; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); +}); diff --git a/README.MD b/README.MD index 3110d0c..4480c21 100644 --- a/README.MD +++ b/README.MD @@ -20,10 +20,17 @@ ```bash tsc ``` +Executing this in the application's root folder will use all settings from `tsconfig.json`. ## Run Run the application. ```bash node dist/app.js +``` + +## Execute tests + +```bash +npm test ``` \ No newline at end of file diff --git a/src/MazeMap.spec.ts b/src/MazeMap.spec.ts new file mode 100644 index 0000000..f54557a --- /dev/null +++ b/src/MazeMap.spec.ts @@ -0,0 +1,190 @@ +import {expect} from "chai"; +import { Direction, MazeMap, MazeTile, MazeValidationError, TileValidationError } from "./MazeMap"; +// import { describe, it } from "mocha"; + +describe("Scenario: validate a single tile", function(){ + describe("When it is valid", function(){ + const tile = new MazeTile("id"); + tile.paths.push(Direction.right); + const result = tile.validate(); + it("then it returns an empty array.", function(){ + expect(result).to.not.be.null; + expect(Array.isArray(result)).to.be.true; + expect(result.length).to.equal(0); + }); + }); + + describe("When id is null", function(){ + const tile = new MazeTile(null); + const result = tile.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(TileValidationError.InvalidID); + }); + }); + + describe("When id is empty", function(){ + const tile = new MazeTile(""); + const result = tile.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(TileValidationError.InvalidID); + }); + }); + + describe("When no path way is given", function(){ + const tile = new MazeTile("id"); + const result = tile.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(TileValidationError.NoPathways); + }); + }); + + describe("When the only path is \"none\"", function(){ + const tile = new MazeTile("id"); + tile.paths.push(Direction.none); + const result = tile.validate(); + it("then it is valid", function(){ + expect(result.length).to.equal(0); + }); + }); +}); + +function createMinimalisticMaze(): MazeMap +{ + const maze = new MazeMap("id"); + let tile = new MazeTile("1"); + tile.paths.push(Direction.right); + maze.layout.push([tile]); + tile = new MazeTile("2"); + tile.paths.push(Direction.left); + maze.layout[0].push(tile); + return maze; +} + +describe("Scenario: validate a whole maze", function(){ + describe("When it is valid", function(){ + const maze = createMinimalisticMaze(); + const result = maze.validate(); + it("then it returns an empty array.", function(){ + expect(result).to.not.be.null; + expect(Array.isArray(result)).to.be.true; + expect(result.length).to.equal(0); + }); + }); + + describe("When the id is null", function(){ + const maze = createMinimalisticMaze(); + maze.id = null; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.InvalidID); + }); + }); + + describe("When the id is empty", function(){ + const maze = createMinimalisticMaze(); + maze.id = ""; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.InvalidID); + }); + }); + + describe("When one tile is invalid", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].id = null; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.InvalidTiles); + }); + }); + + describe("When it is not rectangular shaped", function(){ + const maze = createMinimalisticMaze(); + const tile = new MazeTile("3"); + tile.paths.push(Direction.none); + maze.layout.push([tile]); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.WrongShape); + }); + }); + + describe("When it has a null tile", function(){ + const maze = createMinimalisticMaze(); + const tile = new MazeTile("3"); + tile.paths.push(Direction.none); + maze.layout.push([tile, null]); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.NoHolesAllowed); + }); + }); + + describe("When it has a null row at the end", function(){ + const maze = createMinimalisticMaze(); + maze.layout.push(null); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.NoHolesAllowed); + }); + }); + + describe("When a path leaves the map on the top side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].paths.push(Direction.top); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path leaves the map on the left side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].paths.push(Direction.left); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path leaves the map on the bottom side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].paths.push(Direction.bottom); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path leaves the map on the right side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][1].paths.push(Direction.right); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path does not have an opposite on the other side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][1].paths[0] = Direction.none; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); +}); diff --git a/src/MazeMap.ts b/src/MazeMap.ts index 706e86b..c9a497f 100644 --- a/src/MazeMap.ts +++ b/src/MazeMap.ts @@ -7,17 +7,45 @@ right = 1 << 3, } +export function OppositeDirection(dir:Direction): Direction +{ + switch (dir) { + case Direction.top: return Direction.bottom; + case Direction.bottom: return Direction.top; + case Direction.right: return Direction.left; + case Direction.left: return Direction.right; + case Direction.none: return Direction.none; + default: + throw new Error(`"${dir}" does not have a recognized opposite.`); + } +} + +function DirectionToVector(dir:Direction): number[] +{ + switch (dir) { + case Direction.top: return [-1, 0]; + case Direction.left: return [0, -1]; + case Direction.bottom: return [1, 0]; + case Direction.right: return [0, 1]; + default: return [0, 0]; + } +} + +export enum TileValidationError +{ + InvalidID = "Id must not be null or empty.", + NoPathways = "Tile must have at least one pathway.", +} + export class MazeTile { public paths:Direction[]; public id:string; - public neighbors:Map; constructor(id:string) { this.id = id; this.paths = []; - this.neighbors = new Map(); } public getRepresentation(): number @@ -28,25 +56,60 @@ } return output; } + + public validate(): TileValidationError[] + { + const errors: TileValidationError[] = []; + + if(this.id == null || this.id === "") + { + errors.push(TileValidationError.InvalidID); + } + + if(this.paths.length === 0) + { + errors.push(TileValidationError.NoPathways); + } + + return errors; + } + + public hasDirection(dir:Direction): boolean + { + for (const d of this.paths) { + if(d === dir) + { + return true; + } + } + return false; + } +} + +export enum MazeValidationError +{ + InvalidID = "Id must not be null or empty.", + InvalidTiles = "Maze contains invalid tiles.", + PathWithoutDestination = "Pathways must have a corresponding destination tile.", + WrongShape = "Maze must be shaped like a rectangle.", + NoHolesAllowed = "Maze has null-tiles.", } export class MazeMap { public id:string; - public allTiles:Map; - public cart:MazeTile[][]; + public layout:MazeTile[][]; constructor(id:string) { this.id = id; - this.allTiles = new Map(); - this.cart = []; + this.layout = []; } public getRepresentation(): number[][] { const output:number[][] = []; - for (const row of this.cart) { + for (const row of this.layout) { const newRow:number[] = []; for (const cell of row) { newRow.push(cell.getRepresentation()); @@ -55,4 +118,92 @@ } return output; } + + public validate(): string[] + { + const errors:string[] = []; + + if(this.id == null || this.id === "") { + errors.push(MazeValidationError.InvalidID); + } + + let hasInvalidTiles = false; + let hasNullTiles = false; + for (const row of this.layout) { + if(row == null) + { + hasNullTiles = true; + continue; + } + for (const tile of row) { + if(tile == null) + { + hasNullTiles = true; + continue; + } + if(tile.validate().length > 0) + { + hasInvalidTiles = true; + } + } + } + if(hasInvalidTiles) + { + errors.push(MazeValidationError.InvalidTiles); + } + if(hasNullTiles) + { + errors.push(MazeValidationError.NoHolesAllowed); + } + + let hasWrongShape = false; + for (const row of this.layout) { + if(row == null || row.length !== this.layout[0].length) + { + errors.push(MazeValidationError.WrongShape); + hasWrongShape = true; + break; + } + } + + // Only do the complex path validation if there are no violations of shape requirements. + if(!hasNullTiles && !hasWrongShape) + { + let hasPathsWithoutDestination = false; + for(let i=0;i= this.layout.length || j+y < 0 || j+y >= this.layout[i+x].length) + { + hasPathsWithoutDestination = true; + break; + } + + if(!this.layout[i+x][j+y].hasDirection(opposite)) + { + hasPathsWithoutDestination = true; + break; + } + } + } + } + if(hasPathsWithoutDestination) + { + errors.push(MazeValidationError.PathWithoutDestination); + } + } + + return errors; + } } diff --git a/README.MD b/README.MD index 3110d0c..4480c21 100644 --- a/README.MD +++ b/README.MD @@ -20,10 +20,17 @@ ```bash tsc ``` +Executing this in the application's root folder will use all settings from `tsconfig.json`. ## Run Run the application. ```bash node dist/app.js +``` + +## Execute tests + +```bash +npm test ``` \ No newline at end of file diff --git a/src/MazeMap.spec.ts b/src/MazeMap.spec.ts new file mode 100644 index 0000000..f54557a --- /dev/null +++ b/src/MazeMap.spec.ts @@ -0,0 +1,190 @@ +import {expect} from "chai"; +import { Direction, MazeMap, MazeTile, MazeValidationError, TileValidationError } from "./MazeMap"; +// import { describe, it } from "mocha"; + +describe("Scenario: validate a single tile", function(){ + describe("When it is valid", function(){ + const tile = new MazeTile("id"); + tile.paths.push(Direction.right); + const result = tile.validate(); + it("then it returns an empty array.", function(){ + expect(result).to.not.be.null; + expect(Array.isArray(result)).to.be.true; + expect(result.length).to.equal(0); + }); + }); + + describe("When id is null", function(){ + const tile = new MazeTile(null); + const result = tile.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(TileValidationError.InvalidID); + }); + }); + + describe("When id is empty", function(){ + const tile = new MazeTile(""); + const result = tile.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(TileValidationError.InvalidID); + }); + }); + + describe("When no path way is given", function(){ + const tile = new MazeTile("id"); + const result = tile.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(TileValidationError.NoPathways); + }); + }); + + describe("When the only path is \"none\"", function(){ + const tile = new MazeTile("id"); + tile.paths.push(Direction.none); + const result = tile.validate(); + it("then it is valid", function(){ + expect(result.length).to.equal(0); + }); + }); +}); + +function createMinimalisticMaze(): MazeMap +{ + const maze = new MazeMap("id"); + let tile = new MazeTile("1"); + tile.paths.push(Direction.right); + maze.layout.push([tile]); + tile = new MazeTile("2"); + tile.paths.push(Direction.left); + maze.layout[0].push(tile); + return maze; +} + +describe("Scenario: validate a whole maze", function(){ + describe("When it is valid", function(){ + const maze = createMinimalisticMaze(); + const result = maze.validate(); + it("then it returns an empty array.", function(){ + expect(result).to.not.be.null; + expect(Array.isArray(result)).to.be.true; + expect(result.length).to.equal(0); + }); + }); + + describe("When the id is null", function(){ + const maze = createMinimalisticMaze(); + maze.id = null; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.InvalidID); + }); + }); + + describe("When the id is empty", function(){ + const maze = createMinimalisticMaze(); + maze.id = ""; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.InvalidID); + }); + }); + + describe("When one tile is invalid", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].id = null; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.InvalidTiles); + }); + }); + + describe("When it is not rectangular shaped", function(){ + const maze = createMinimalisticMaze(); + const tile = new MazeTile("3"); + tile.paths.push(Direction.none); + maze.layout.push([tile]); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.WrongShape); + }); + }); + + describe("When it has a null tile", function(){ + const maze = createMinimalisticMaze(); + const tile = new MazeTile("3"); + tile.paths.push(Direction.none); + maze.layout.push([tile, null]); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.NoHolesAllowed); + }); + }); + + describe("When it has a null row at the end", function(){ + const maze = createMinimalisticMaze(); + maze.layout.push(null); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.NoHolesAllowed); + }); + }); + + describe("When a path leaves the map on the top side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].paths.push(Direction.top); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path leaves the map on the left side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].paths.push(Direction.left); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path leaves the map on the bottom side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][0].paths.push(Direction.bottom); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path leaves the map on the right side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][1].paths.push(Direction.right); + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); + + describe("When a path does not have an opposite on the other side", function(){ + const maze = createMinimalisticMaze(); + maze.layout[0][1].paths[0] = Direction.none; + const result = maze.validate(); + it("then it is invalid", function(){ + expect(result.length).to.be.greaterThan(0); + expect(result).to.contain(MazeValidationError.PathWithoutDestination); + }); + }); +}); diff --git a/src/MazeMap.ts b/src/MazeMap.ts index 706e86b..c9a497f 100644 --- a/src/MazeMap.ts +++ b/src/MazeMap.ts @@ -7,17 +7,45 @@ right = 1 << 3, } +export function OppositeDirection(dir:Direction): Direction +{ + switch (dir) { + case Direction.top: return Direction.bottom; + case Direction.bottom: return Direction.top; + case Direction.right: return Direction.left; + case Direction.left: return Direction.right; + case Direction.none: return Direction.none; + default: + throw new Error(`"${dir}" does not have a recognized opposite.`); + } +} + +function DirectionToVector(dir:Direction): number[] +{ + switch (dir) { + case Direction.top: return [-1, 0]; + case Direction.left: return [0, -1]; + case Direction.bottom: return [1, 0]; + case Direction.right: return [0, 1]; + default: return [0, 0]; + } +} + +export enum TileValidationError +{ + InvalidID = "Id must not be null or empty.", + NoPathways = "Tile must have at least one pathway.", +} + export class MazeTile { public paths:Direction[]; public id:string; - public neighbors:Map; constructor(id:string) { this.id = id; this.paths = []; - this.neighbors = new Map(); } public getRepresentation(): number @@ -28,25 +56,60 @@ } return output; } + + public validate(): TileValidationError[] + { + const errors: TileValidationError[] = []; + + if(this.id == null || this.id === "") + { + errors.push(TileValidationError.InvalidID); + } + + if(this.paths.length === 0) + { + errors.push(TileValidationError.NoPathways); + } + + return errors; + } + + public hasDirection(dir:Direction): boolean + { + for (const d of this.paths) { + if(d === dir) + { + return true; + } + } + return false; + } +} + +export enum MazeValidationError +{ + InvalidID = "Id must not be null or empty.", + InvalidTiles = "Maze contains invalid tiles.", + PathWithoutDestination = "Pathways must have a corresponding destination tile.", + WrongShape = "Maze must be shaped like a rectangle.", + NoHolesAllowed = "Maze has null-tiles.", } export class MazeMap { public id:string; - public allTiles:Map; - public cart:MazeTile[][]; + public layout:MazeTile[][]; constructor(id:string) { this.id = id; - this.allTiles = new Map(); - this.cart = []; + this.layout = []; } public getRepresentation(): number[][] { const output:number[][] = []; - for (const row of this.cart) { + for (const row of this.layout) { const newRow:number[] = []; for (const cell of row) { newRow.push(cell.getRepresentation()); @@ -55,4 +118,92 @@ } return output; } + + public validate(): string[] + { + const errors:string[] = []; + + if(this.id == null || this.id === "") { + errors.push(MazeValidationError.InvalidID); + } + + let hasInvalidTiles = false; + let hasNullTiles = false; + for (const row of this.layout) { + if(row == null) + { + hasNullTiles = true; + continue; + } + for (const tile of row) { + if(tile == null) + { + hasNullTiles = true; + continue; + } + if(tile.validate().length > 0) + { + hasInvalidTiles = true; + } + } + } + if(hasInvalidTiles) + { + errors.push(MazeValidationError.InvalidTiles); + } + if(hasNullTiles) + { + errors.push(MazeValidationError.NoHolesAllowed); + } + + let hasWrongShape = false; + for (const row of this.layout) { + if(row == null || row.length !== this.layout[0].length) + { + errors.push(MazeValidationError.WrongShape); + hasWrongShape = true; + break; + } + } + + // Only do the complex path validation if there are no violations of shape requirements. + if(!hasNullTiles && !hasWrongShape) + { + let hasPathsWithoutDestination = false; + for(let i=0;i= this.layout.length || j+y < 0 || j+y >= this.layout[i+x].length) + { + hasPathsWithoutDestination = true; + break; + } + + if(!this.layout[i+x][j+y].hasDirection(opposite)) + { + hasPathsWithoutDestination = true; + break; + } + } + } + } + if(hasPathsWithoutDestination) + { + errors.push(MazeValidationError.PathWithoutDestination); + } + } + + return errors; + } } diff --git a/tslint.json b/tslint.json index 8c73a4d..13e0631 100644 --- a/tslint.json +++ b/tslint.json @@ -12,7 +12,8 @@ "no-bitwise": false, "max-classes-per-file":false, "only-arrow-functions": false, - "no-unused-expression": false + "no-unused-expression": false, + "typedef": [true, "parameter", "member-variable-declaration"] }, "rulesDirectory": [], "linterOptions": {