diff --git a/README.MD b/README.MD index 4480c21..150ee94 100644 --- a/README.MD +++ b/README.MD @@ -1,10 +1,15 @@ Labyrinth ========= +## About +*Labyrinth* is a web api to navigate through a maze. + + +# How to run ## Install -Install typescript and tslint globally, if you haven't aready. +Install `typescript` and `tslint` globally, if you haven't aready. ```bash npm install -g typescript tslint ``` @@ -20,7 +25,7 @@ ```bash tsc ``` -Executing this in the application's root folder will use all settings from `tsconfig.json`. +Executing this in the application's root folder will apply the settings from `tsconfig.json`. ## Run diff --git a/README.MD b/README.MD index 4480c21..150ee94 100644 --- a/README.MD +++ b/README.MD @@ -1,10 +1,15 @@ Labyrinth ========= +## About +*Labyrinth* is a web api to navigate through a maze. + + +# How to run ## Install -Install typescript and tslint globally, if you haven't aready. +Install `typescript` and `tslint` globally, if you haven't aready. ```bash npm install -g typescript tslint ``` @@ -20,7 +25,7 @@ ```bash tsc ``` -Executing this in the application's root folder will use all settings from `tsconfig.json`. +Executing this in the application's root folder will apply the settings from `tsconfig.json`. ## Run diff --git a/src/GameServer.spec.ts b/src/GameServer.spec.ts index b105065..76c8775 100644 --- a/src/GameServer.spec.ts +++ b/src/GameServer.spec.ts @@ -27,6 +27,8 @@ tile.paths.push(Direction.top); maze.layout[1].push(tile); + maze.startPosition = [0,0]; + return maze; } @@ -37,6 +39,27 @@ return server; } +describe("Scenario: Accessing current position", function(){ + const userID = "UserID"; + const mapID = "lvl1"; + describe("When a game has been started for a player", function(){ + const games = createGameServer(); + games.startGame(userID, mapID); + it("then the position must not be null", function(){ + const position = games.currentPosition(userID); + expect(position).to.be.not.null; + }); + }); + + describe("When no game for player has been started", function(){ + const games = createGameServer(); + it("then null should be returned for that player", function(){ + const position = games.currentPosition(userID); + expect(position).to.be.null; + }); + }); +}); + describe("Scenario: Starting the game", function(){ const userID = "UserID"; const mapID = "lvl1"; @@ -58,49 +81,48 @@ describe("Scenario: Navigating in ongoing game", function(){ const userID = "UserID"; const mapID = "lvl1"; - const games = createGameServer(); - const startPosition = games.startGame(userID, mapID); describe("When player navigates in existing direction", function(){ + const games = createGameServer(); + const startPosition = games.startGame(userID, mapID); const naviagtionDirection = startPosition.paths[0]; - const nextPosition = games.navigate(userID, startPosition.id, naviagtionDirection); + const nextPosition = games.navigate(userID, naviagtionDirection); it("then the new position has to have a different id", function(){ expect(nextPosition.id).to.not.equal(startPosition.id); }); - it("then the new position must have pathway back", function(){ - let hasOpposite:boolean = false; - for (const dir of nextPosition.paths) { - switch (dir) { - case Direction.top: - if(naviagtionDirection === Direction.bottom) - { - hasOpposite = true; - } - break; - case Direction.left: - if(naviagtionDirection === Direction.right) - { - hasOpposite = true; - } - break; - case Direction.bottom: - if(naviagtionDirection === Direction.top) - { - hasOpposite = true; - } - break; - case Direction.right: - if(naviagtionDirection === Direction.left) - { - hasOpposite = true; - } - break; - default: - throw new Error(`Direction "${dir}" not recognized.`); - } + it("then the new position must be remembered for the user", function(){ + const current = games.currentPosition(userID); + expect(current.id).to.equal(nextPosition.id); + }); + }); + + describe("When a player navigates outside the map boundaries", function(){ + const games = createGameServer(); + games.startGame(userID, mapID); + it("then it should throw an exception", function(){ + function expectedToFail(){ + games.navigate(userID, Direction.left); } - expect(hasOpposite).to.be.true; + expect(expectedToFail).to.throw(/.*invalid direction.*/i); + }); + }); + + describe("When a player navigates inside the map in non existing direction", function(){ + const games = createGameServer(); + const maze2 = createTrivialMap(); + maze2.id = "lvl2"; + maze2.layout[0][0].paths = maze2.layout[0][0].paths.filter(p => p !== Direction.bottom); + maze2.layout[1][0].paths = maze2.layout[1][0].paths.filter(p => p !== Direction.top); + games.addMap(maze2); + + games.startGame(userID, maze2.id); + + it("then it should throw an exception", function(){ + function expectedToFail(){ + games.navigate(userID, Direction.bottom); + } + expect(expectedToFail).to.throw(/.*invalid direction.*/i); }); }); }); diff --git a/README.MD b/README.MD index 4480c21..150ee94 100644 --- a/README.MD +++ b/README.MD @@ -1,10 +1,15 @@ Labyrinth ========= +## About +*Labyrinth* is a web api to navigate through a maze. + + +# How to run ## Install -Install typescript and tslint globally, if you haven't aready. +Install `typescript` and `tslint` globally, if you haven't aready. ```bash npm install -g typescript tslint ``` @@ -20,7 +25,7 @@ ```bash tsc ``` -Executing this in the application's root folder will use all settings from `tsconfig.json`. +Executing this in the application's root folder will apply the settings from `tsconfig.json`. ## Run diff --git a/src/GameServer.spec.ts b/src/GameServer.spec.ts index b105065..76c8775 100644 --- a/src/GameServer.spec.ts +++ b/src/GameServer.spec.ts @@ -27,6 +27,8 @@ tile.paths.push(Direction.top); maze.layout[1].push(tile); + maze.startPosition = [0,0]; + return maze; } @@ -37,6 +39,27 @@ return server; } +describe("Scenario: Accessing current position", function(){ + const userID = "UserID"; + const mapID = "lvl1"; + describe("When a game has been started for a player", function(){ + const games = createGameServer(); + games.startGame(userID, mapID); + it("then the position must not be null", function(){ + const position = games.currentPosition(userID); + expect(position).to.be.not.null; + }); + }); + + describe("When no game for player has been started", function(){ + const games = createGameServer(); + it("then null should be returned for that player", function(){ + const position = games.currentPosition(userID); + expect(position).to.be.null; + }); + }); +}); + describe("Scenario: Starting the game", function(){ const userID = "UserID"; const mapID = "lvl1"; @@ -58,49 +81,48 @@ describe("Scenario: Navigating in ongoing game", function(){ const userID = "UserID"; const mapID = "lvl1"; - const games = createGameServer(); - const startPosition = games.startGame(userID, mapID); describe("When player navigates in existing direction", function(){ + const games = createGameServer(); + const startPosition = games.startGame(userID, mapID); const naviagtionDirection = startPosition.paths[0]; - const nextPosition = games.navigate(userID, startPosition.id, naviagtionDirection); + const nextPosition = games.navigate(userID, naviagtionDirection); it("then the new position has to have a different id", function(){ expect(nextPosition.id).to.not.equal(startPosition.id); }); - it("then the new position must have pathway back", function(){ - let hasOpposite:boolean = false; - for (const dir of nextPosition.paths) { - switch (dir) { - case Direction.top: - if(naviagtionDirection === Direction.bottom) - { - hasOpposite = true; - } - break; - case Direction.left: - if(naviagtionDirection === Direction.right) - { - hasOpposite = true; - } - break; - case Direction.bottom: - if(naviagtionDirection === Direction.top) - { - hasOpposite = true; - } - break; - case Direction.right: - if(naviagtionDirection === Direction.left) - { - hasOpposite = true; - } - break; - default: - throw new Error(`Direction "${dir}" not recognized.`); - } + it("then the new position must be remembered for the user", function(){ + const current = games.currentPosition(userID); + expect(current.id).to.equal(nextPosition.id); + }); + }); + + describe("When a player navigates outside the map boundaries", function(){ + const games = createGameServer(); + games.startGame(userID, mapID); + it("then it should throw an exception", function(){ + function expectedToFail(){ + games.navigate(userID, Direction.left); } - expect(hasOpposite).to.be.true; + expect(expectedToFail).to.throw(/.*invalid direction.*/i); + }); + }); + + describe("When a player navigates inside the map in non existing direction", function(){ + const games = createGameServer(); + const maze2 = createTrivialMap(); + maze2.id = "lvl2"; + maze2.layout[0][0].paths = maze2.layout[0][0].paths.filter(p => p !== Direction.bottom); + maze2.layout[1][0].paths = maze2.layout[1][0].paths.filter(p => p !== Direction.top); + games.addMap(maze2); + + games.startGame(userID, maze2.id); + + it("then it should throw an exception", function(){ + function expectedToFail(){ + games.navigate(userID, Direction.bottom); + } + expect(expectedToFail).to.throw(/.*invalid direction.*/i); }); }); }); diff --git a/src/GameServer.ts b/src/GameServer.ts index c0479cf..04a0818 100644 --- a/src/GameServer.ts +++ b/src/GameServer.ts @@ -1,5 +1,6 @@ import { Direction, + DirectionToVector, MazeMap, MazeTile, } from "./MazeMap"; @@ -20,6 +21,8 @@ export class GameServer { + // By keeping mapList private, we can ensure, + // that the server contains only valid maps. private mapList: MazeMap[]; private states: GameState[]; @@ -73,11 +76,35 @@ return start; } - public navigate(userID: string, currentTile:string, direction:Direction): MazeTile + public navigate(userID: string, direction:Direction): MazeTile { - const newPosition = new MazeTile(""); - newPosition.paths.push(Direction.left); - return newPosition; + const state = this.states.find(s => s.userID === userID); + if(state == null){ + return null; + } + const vec = DirectionToVector(direction); + const map = this.mapList.find(m => m.id === state.mapID); + let i:number; + let j:number; + let tile:MazeTile; + for (i = 0; i < map.layout.length && tile == null; i++) { + const row = map.layout[i]; + for (j = 0; j < row.length; j++) { + if(row[j].id === state.tileID) + { + tile = row[j]; + break; + } + } + } + + if(!tile.hasDirection(direction)){ + throw new Error("Invalid direction."); + } + + const nextTile = map.layout[i+vec[0]][j+vec[1]]; + state.tileID = nextTile.id; + return nextTile; } public currentPosition(userID: string): MazeTile diff --git a/README.MD b/README.MD index 4480c21..150ee94 100644 --- a/README.MD +++ b/README.MD @@ -1,10 +1,15 @@ Labyrinth ========= +## About +*Labyrinth* is a web api to navigate through a maze. + + +# How to run ## Install -Install typescript and tslint globally, if you haven't aready. +Install `typescript` and `tslint` globally, if you haven't aready. ```bash npm install -g typescript tslint ``` @@ -20,7 +25,7 @@ ```bash tsc ``` -Executing this in the application's root folder will use all settings from `tsconfig.json`. +Executing this in the application's root folder will apply the settings from `tsconfig.json`. ## Run diff --git a/src/GameServer.spec.ts b/src/GameServer.spec.ts index b105065..76c8775 100644 --- a/src/GameServer.spec.ts +++ b/src/GameServer.spec.ts @@ -27,6 +27,8 @@ tile.paths.push(Direction.top); maze.layout[1].push(tile); + maze.startPosition = [0,0]; + return maze; } @@ -37,6 +39,27 @@ return server; } +describe("Scenario: Accessing current position", function(){ + const userID = "UserID"; + const mapID = "lvl1"; + describe("When a game has been started for a player", function(){ + const games = createGameServer(); + games.startGame(userID, mapID); + it("then the position must not be null", function(){ + const position = games.currentPosition(userID); + expect(position).to.be.not.null; + }); + }); + + describe("When no game for player has been started", function(){ + const games = createGameServer(); + it("then null should be returned for that player", function(){ + const position = games.currentPosition(userID); + expect(position).to.be.null; + }); + }); +}); + describe("Scenario: Starting the game", function(){ const userID = "UserID"; const mapID = "lvl1"; @@ -58,49 +81,48 @@ describe("Scenario: Navigating in ongoing game", function(){ const userID = "UserID"; const mapID = "lvl1"; - const games = createGameServer(); - const startPosition = games.startGame(userID, mapID); describe("When player navigates in existing direction", function(){ + const games = createGameServer(); + const startPosition = games.startGame(userID, mapID); const naviagtionDirection = startPosition.paths[0]; - const nextPosition = games.navigate(userID, startPosition.id, naviagtionDirection); + const nextPosition = games.navigate(userID, naviagtionDirection); it("then the new position has to have a different id", function(){ expect(nextPosition.id).to.not.equal(startPosition.id); }); - it("then the new position must have pathway back", function(){ - let hasOpposite:boolean = false; - for (const dir of nextPosition.paths) { - switch (dir) { - case Direction.top: - if(naviagtionDirection === Direction.bottom) - { - hasOpposite = true; - } - break; - case Direction.left: - if(naviagtionDirection === Direction.right) - { - hasOpposite = true; - } - break; - case Direction.bottom: - if(naviagtionDirection === Direction.top) - { - hasOpposite = true; - } - break; - case Direction.right: - if(naviagtionDirection === Direction.left) - { - hasOpposite = true; - } - break; - default: - throw new Error(`Direction "${dir}" not recognized.`); - } + it("then the new position must be remembered for the user", function(){ + const current = games.currentPosition(userID); + expect(current.id).to.equal(nextPosition.id); + }); + }); + + describe("When a player navigates outside the map boundaries", function(){ + const games = createGameServer(); + games.startGame(userID, mapID); + it("then it should throw an exception", function(){ + function expectedToFail(){ + games.navigate(userID, Direction.left); } - expect(hasOpposite).to.be.true; + expect(expectedToFail).to.throw(/.*invalid direction.*/i); + }); + }); + + describe("When a player navigates inside the map in non existing direction", function(){ + const games = createGameServer(); + const maze2 = createTrivialMap(); + maze2.id = "lvl2"; + maze2.layout[0][0].paths = maze2.layout[0][0].paths.filter(p => p !== Direction.bottom); + maze2.layout[1][0].paths = maze2.layout[1][0].paths.filter(p => p !== Direction.top); + games.addMap(maze2); + + games.startGame(userID, maze2.id); + + it("then it should throw an exception", function(){ + function expectedToFail(){ + games.navigate(userID, Direction.bottom); + } + expect(expectedToFail).to.throw(/.*invalid direction.*/i); }); }); }); diff --git a/src/GameServer.ts b/src/GameServer.ts index c0479cf..04a0818 100644 --- a/src/GameServer.ts +++ b/src/GameServer.ts @@ -1,5 +1,6 @@ import { Direction, + DirectionToVector, MazeMap, MazeTile, } from "./MazeMap"; @@ -20,6 +21,8 @@ export class GameServer { + // By keeping mapList private, we can ensure, + // that the server contains only valid maps. private mapList: MazeMap[]; private states: GameState[]; @@ -73,11 +76,35 @@ return start; } - public navigate(userID: string, currentTile:string, direction:Direction): MazeTile + public navigate(userID: string, direction:Direction): MazeTile { - const newPosition = new MazeTile(""); - newPosition.paths.push(Direction.left); - return newPosition; + const state = this.states.find(s => s.userID === userID); + if(state == null){ + return null; + } + const vec = DirectionToVector(direction); + const map = this.mapList.find(m => m.id === state.mapID); + let i:number; + let j:number; + let tile:MazeTile; + for (i = 0; i < map.layout.length && tile == null; i++) { + const row = map.layout[i]; + for (j = 0; j < row.length; j++) { + if(row[j].id === state.tileID) + { + tile = row[j]; + break; + } + } + } + + if(!tile.hasDirection(direction)){ + throw new Error("Invalid direction."); + } + + const nextTile = map.layout[i+vec[0]][j+vec[1]]; + state.tileID = nextTile.id; + return nextTile; } public currentPosition(userID: string): MazeTile diff --git a/src/MazeMap.ts b/src/MazeMap.ts index 7110157..fdae093 100644 --- a/src/MazeMap.ts +++ b/src/MazeMap.ts @@ -20,7 +20,7 @@ } } -function DirectionToVector(dir:Direction): number[] +export function DirectionToVector(dir:Direction): number[] { switch (dir) { case Direction.top: return [-1, 0];