diff --git a/.vscode/launch.json b/.vscode/launch.json index 2eb7579..95cccf8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,6 +7,22 @@ { "type": "node", "request": "launch", + "name": "Mocha Tests", + "preLaunchTask": "build files", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceFolder}/dist/**/*.spec.js" + ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", "name": "Server starten", "program": "${workspaceFolder}/dist/app.js", "preLaunchTask": "build files", diff --git a/.vscode/launch.json b/.vscode/launch.json index 2eb7579..95cccf8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,6 +7,22 @@ { "type": "node", "request": "launch", + "name": "Mocha Tests", + "preLaunchTask": "build files", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceFolder}/dist/**/*.spec.js" + ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", "name": "Server starten", "program": "${workspaceFolder}/dist/app.js", "preLaunchTask": "build files", diff --git a/src/GameServer.spec.ts b/src/GameServer.spec.ts index 743c0ff..20f7f0a 100644 --- a/src/GameServer.spec.ts +++ b/src/GameServer.spec.ts @@ -206,3 +206,22 @@ }); }); }); + +describe("Scenario: List existing maps", function(){ + describe("When no map is inserted", function(){ + const games = new GameServer(); + it("then it should return an empty list", function(){ + const result = games.listMaps(); + expect(result).to.be.not.null; + expect(result).to.be.empty; + }); + }); + + describe("When one map is inserted", function(){ + const games = createGameServer(); + it("then it should return a list with only one id", function(){ + const result = games.listMaps(); + expect(result).to.contain("lvl1"); + }); + }); +}); diff --git a/.vscode/launch.json b/.vscode/launch.json index 2eb7579..95cccf8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,6 +7,22 @@ { "type": "node", "request": "launch", + "name": "Mocha Tests", + "preLaunchTask": "build files", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceFolder}/dist/**/*.spec.js" + ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", "name": "Server starten", "program": "${workspaceFolder}/dist/app.js", "preLaunchTask": "build files", diff --git a/src/GameServer.spec.ts b/src/GameServer.spec.ts index 743c0ff..20f7f0a 100644 --- a/src/GameServer.spec.ts +++ b/src/GameServer.spec.ts @@ -206,3 +206,22 @@ }); }); }); + +describe("Scenario: List existing maps", function(){ + describe("When no map is inserted", function(){ + const games = new GameServer(); + it("then it should return an empty list", function(){ + const result = games.listMaps(); + expect(result).to.be.not.null; + expect(result).to.be.empty; + }); + }); + + describe("When one map is inserted", function(){ + const games = createGameServer(); + it("then it should return a list with only one id", function(){ + const result = games.listMaps(); + expect(result).to.contain("lvl1"); + }); + }); +}); diff --git a/src/GameServer.ts b/src/GameServer.ts index 5884e9d..4d59d2f 100644 --- a/src/GameServer.ts +++ b/src/GameServer.ts @@ -124,6 +124,11 @@ return tile; } + public listMaps(): string[] + { + return this.mapList.map(m => m.id); + } + /** * Makes sure, that a userID is acceptable. * If not throw the appropriate error. diff --git a/.vscode/launch.json b/.vscode/launch.json index 2eb7579..95cccf8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,6 +7,22 @@ { "type": "node", "request": "launch", + "name": "Mocha Tests", + "preLaunchTask": "build files", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceFolder}/dist/**/*.spec.js" + ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", "name": "Server starten", "program": "${workspaceFolder}/dist/app.js", "preLaunchTask": "build files", diff --git a/src/GameServer.spec.ts b/src/GameServer.spec.ts index 743c0ff..20f7f0a 100644 --- a/src/GameServer.spec.ts +++ b/src/GameServer.spec.ts @@ -206,3 +206,22 @@ }); }); }); + +describe("Scenario: List existing maps", function(){ + describe("When no map is inserted", function(){ + const games = new GameServer(); + it("then it should return an empty list", function(){ + const result = games.listMaps(); + expect(result).to.be.not.null; + expect(result).to.be.empty; + }); + }); + + describe("When one map is inserted", function(){ + const games = createGameServer(); + it("then it should return a list with only one id", function(){ + const result = games.listMaps(); + expect(result).to.contain("lvl1"); + }); + }); +}); diff --git a/src/GameServer.ts b/src/GameServer.ts index 5884e9d..4d59d2f 100644 --- a/src/GameServer.ts +++ b/src/GameServer.ts @@ -124,6 +124,11 @@ return tile; } + public listMaps(): string[] + { + return this.mapList.map(m => m.id); + } + /** * Makes sure, that a userID is acceptable. * If not throw the appropriate error. diff --git a/src/SampleMaps.spec.ts b/src/SampleMaps.spec.ts new file mode 100644 index 0000000..f7df2f2 --- /dev/null +++ b/src/SampleMaps.spec.ts @@ -0,0 +1,13 @@ +import { expect } from "chai"; +import { createSampleMaps } from "./SampleMaps"; + +describe("Validate sample maps", function(){ + const maps = createSampleMaps(); + + for (const map of maps) { + it(`validate map "${map.id}"`, function(){ + const validationResult = map.validate(); + expect(validationResult).to.be.empty; + }); + } +}); diff --git a/.vscode/launch.json b/.vscode/launch.json index 2eb7579..95cccf8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,6 +7,22 @@ { "type": "node", "request": "launch", + "name": "Mocha Tests", + "preLaunchTask": "build files", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceFolder}/dist/**/*.spec.js" + ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", "name": "Server starten", "program": "${workspaceFolder}/dist/app.js", "preLaunchTask": "build files", diff --git a/src/GameServer.spec.ts b/src/GameServer.spec.ts index 743c0ff..20f7f0a 100644 --- a/src/GameServer.spec.ts +++ b/src/GameServer.spec.ts @@ -206,3 +206,22 @@ }); }); }); + +describe("Scenario: List existing maps", function(){ + describe("When no map is inserted", function(){ + const games = new GameServer(); + it("then it should return an empty list", function(){ + const result = games.listMaps(); + expect(result).to.be.not.null; + expect(result).to.be.empty; + }); + }); + + describe("When one map is inserted", function(){ + const games = createGameServer(); + it("then it should return a list with only one id", function(){ + const result = games.listMaps(); + expect(result).to.contain("lvl1"); + }); + }); +}); diff --git a/src/GameServer.ts b/src/GameServer.ts index 5884e9d..4d59d2f 100644 --- a/src/GameServer.ts +++ b/src/GameServer.ts @@ -124,6 +124,11 @@ return tile; } + public listMaps(): string[] + { + return this.mapList.map(m => m.id); + } + /** * Makes sure, that a userID is acceptable. * If not throw the appropriate error. diff --git a/src/SampleMaps.spec.ts b/src/SampleMaps.spec.ts new file mode 100644 index 0000000..f7df2f2 --- /dev/null +++ b/src/SampleMaps.spec.ts @@ -0,0 +1,13 @@ +import { expect } from "chai"; +import { createSampleMaps } from "./SampleMaps"; + +describe("Validate sample maps", function(){ + const maps = createSampleMaps(); + + for (const map of maps) { + it(`validate map "${map.id}"`, function(){ + const validationResult = map.validate(); + expect(validationResult).to.be.empty; + }); + } +}); diff --git a/src/SampleMaps.ts b/src/SampleMaps.ts new file mode 100644 index 0000000..b5630fd --- /dev/null +++ b/src/SampleMaps.ts @@ -0,0 +1,160 @@ +import { + Direction, + MazeMap, + MazeTile, +} from "./MazeMap"; + +// define a few shorthand aliases +const t = Direction.top; +const l = Direction.left; +const b = Direction.bottom; +const r = Direction.right; +const n = Direction.none; + +function numberToDirections(a:number): Direction[] +{ + if(a === (Direction.none as number)){ + return [Direction.none]; + } + + const output:Direction[] = []; + if(a & Direction.top){ + output.push(Direction.top); + } + if(a & Direction.left){ + output.push(Direction.left); + } + if(a & Direction.bottom){ + output.push(Direction.bottom); + } + if(a & Direction.right){ + output.push(Direction.right); + } + return output; +} + +function numberMapToTileMap(map:number[][]): MazeTile[][] +{ + const output:MazeTile[][] = []; + let i = 0; + for (const row of map) { + const newRow = []; + for (const a of row) { + const tile = new MazeTile(i+""); + tile.paths = numberToDirections(a); + newRow.push(tile); + i++; + } + output.push(newRow); + } + + return output; +} + +function parseStringMap(map:string): MazeTile[][] +{ + map = map.replace("\r", ""); + const rows = map.split("\n"); + + const cells = rows.map(row => row.split("")); + + let idCounter = 0; + const output:MazeTile[][] = []; + for (let i = 0; i < (cells.length+1)/2; i++) { + output.push([]); + for (let j = 0; j < (cells[i].length+1)/2; j++) { + output[i].push(new MazeTile(idCounter+"")); + idCounter++; + } + } + + for (let i = 0; i < cells.length; i++) { + const row = cells[i]; + for (let j = 0; j < row.length; j++) { + const cell = row[j]; + if(i % 2 === 0) + { + // we're in a row with vertical pathways + if(j % 2 === 1) + { + // we're in a cell with a connection definition + if(cell === "-"){ + output[i/2][(j-1)/2].paths.push(Direction.right); + output[i/2][(j+1)/2].paths.push(Direction.left); + } + } + } + else + { + // we're in a row with horizontal pathways + if(j % 2 === 0) + { + // we're in a cell with a connection definition + if(cell === "|") + { + output[(i-1)/2][j/2].paths.push(Direction.bottom); + output[(i+1)/2][j/2].paths.push(Direction.top); + } + } + } + } + } + + // make sure every tile has at least one direction element + for (const row of output) { + for (const tile of row) { + if(tile.paths.length === 0){ + tile.paths.push(Direction.none); + } + } + } + + return output; +} + +function createLvl1(): MazeMap +{ + const maze = new MazeMap("lvl1"); +/* ++-+ +| | ++-+ + */ + const map = [ + [b|r, l|b], + [t|r, t|l], + ]; + maze.layout = numberMapToTileMap(map); + + maze.startPosition = [0,0]; + + return maze; +} + +function createLvl2(): MazeMap +{ + const maze = new MazeMap("lvl2"); + + const map = + "+-+-+\n"+ + "| | |\n"+ + "+ + +\n"+ + "| |\n"+ + "+-+-+"; + + maze.layout = parseStringMap(map); + + maze.startPosition = [0,0]; + + return maze; +} + +export function createSampleMaps(): MazeMap[] +{ + const output:MazeMap[] = []; + + output.push(createLvl1()); + output.push(createLvl2()); + + return output; +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 2eb7579..95cccf8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,6 +7,22 @@ { "type": "node", "request": "launch", + "name": "Mocha Tests", + "preLaunchTask": "build files", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceFolder}/dist/**/*.spec.js" + ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", "name": "Server starten", "program": "${workspaceFolder}/dist/app.js", "preLaunchTask": "build files", diff --git a/src/GameServer.spec.ts b/src/GameServer.spec.ts index 743c0ff..20f7f0a 100644 --- a/src/GameServer.spec.ts +++ b/src/GameServer.spec.ts @@ -206,3 +206,22 @@ }); }); }); + +describe("Scenario: List existing maps", function(){ + describe("When no map is inserted", function(){ + const games = new GameServer(); + it("then it should return an empty list", function(){ + const result = games.listMaps(); + expect(result).to.be.not.null; + expect(result).to.be.empty; + }); + }); + + describe("When one map is inserted", function(){ + const games = createGameServer(); + it("then it should return a list with only one id", function(){ + const result = games.listMaps(); + expect(result).to.contain("lvl1"); + }); + }); +}); diff --git a/src/GameServer.ts b/src/GameServer.ts index 5884e9d..4d59d2f 100644 --- a/src/GameServer.ts +++ b/src/GameServer.ts @@ -124,6 +124,11 @@ return tile; } + public listMaps(): string[] + { + return this.mapList.map(m => m.id); + } + /** * Makes sure, that a userID is acceptable. * If not throw the appropriate error. diff --git a/src/SampleMaps.spec.ts b/src/SampleMaps.spec.ts new file mode 100644 index 0000000..f7df2f2 --- /dev/null +++ b/src/SampleMaps.spec.ts @@ -0,0 +1,13 @@ +import { expect } from "chai"; +import { createSampleMaps } from "./SampleMaps"; + +describe("Validate sample maps", function(){ + const maps = createSampleMaps(); + + for (const map of maps) { + it(`validate map "${map.id}"`, function(){ + const validationResult = map.validate(); + expect(validationResult).to.be.empty; + }); + } +}); diff --git a/src/SampleMaps.ts b/src/SampleMaps.ts new file mode 100644 index 0000000..b5630fd --- /dev/null +++ b/src/SampleMaps.ts @@ -0,0 +1,160 @@ +import { + Direction, + MazeMap, + MazeTile, +} from "./MazeMap"; + +// define a few shorthand aliases +const t = Direction.top; +const l = Direction.left; +const b = Direction.bottom; +const r = Direction.right; +const n = Direction.none; + +function numberToDirections(a:number): Direction[] +{ + if(a === (Direction.none as number)){ + return [Direction.none]; + } + + const output:Direction[] = []; + if(a & Direction.top){ + output.push(Direction.top); + } + if(a & Direction.left){ + output.push(Direction.left); + } + if(a & Direction.bottom){ + output.push(Direction.bottom); + } + if(a & Direction.right){ + output.push(Direction.right); + } + return output; +} + +function numberMapToTileMap(map:number[][]): MazeTile[][] +{ + const output:MazeTile[][] = []; + let i = 0; + for (const row of map) { + const newRow = []; + for (const a of row) { + const tile = new MazeTile(i+""); + tile.paths = numberToDirections(a); + newRow.push(tile); + i++; + } + output.push(newRow); + } + + return output; +} + +function parseStringMap(map:string): MazeTile[][] +{ + map = map.replace("\r", ""); + const rows = map.split("\n"); + + const cells = rows.map(row => row.split("")); + + let idCounter = 0; + const output:MazeTile[][] = []; + for (let i = 0; i < (cells.length+1)/2; i++) { + output.push([]); + for (let j = 0; j < (cells[i].length+1)/2; j++) { + output[i].push(new MazeTile(idCounter+"")); + idCounter++; + } + } + + for (let i = 0; i < cells.length; i++) { + const row = cells[i]; + for (let j = 0; j < row.length; j++) { + const cell = row[j]; + if(i % 2 === 0) + { + // we're in a row with vertical pathways + if(j % 2 === 1) + { + // we're in a cell with a connection definition + if(cell === "-"){ + output[i/2][(j-1)/2].paths.push(Direction.right); + output[i/2][(j+1)/2].paths.push(Direction.left); + } + } + } + else + { + // we're in a row with horizontal pathways + if(j % 2 === 0) + { + // we're in a cell with a connection definition + if(cell === "|") + { + output[(i-1)/2][j/2].paths.push(Direction.bottom); + output[(i+1)/2][j/2].paths.push(Direction.top); + } + } + } + } + } + + // make sure every tile has at least one direction element + for (const row of output) { + for (const tile of row) { + if(tile.paths.length === 0){ + tile.paths.push(Direction.none); + } + } + } + + return output; +} + +function createLvl1(): MazeMap +{ + const maze = new MazeMap("lvl1"); +/* ++-+ +| | ++-+ + */ + const map = [ + [b|r, l|b], + [t|r, t|l], + ]; + maze.layout = numberMapToTileMap(map); + + maze.startPosition = [0,0]; + + return maze; +} + +function createLvl2(): MazeMap +{ + const maze = new MazeMap("lvl2"); + + const map = + "+-+-+\n"+ + "| | |\n"+ + "+ + +\n"+ + "| |\n"+ + "+-+-+"; + + maze.layout = parseStringMap(map); + + maze.startPosition = [0,0]; + + return maze; +} + +export function createSampleMaps(): MazeMap[] +{ + const output:MazeMap[] = []; + + output.push(createLvl1()); + output.push(createLvl2()); + + return output; +} diff --git a/src/app.ts b/src/app.ts index 67fa8e6..84527cf 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,12 +2,16 @@ import * as graphqlHTTP from "express-graphql"; import {buildSchema} from "graphql"; import { GameServer } from "./GameServer"; +import { createSampleMaps } from "./SampleMaps"; const port = 8080; const apiRoot = "/graphql"; const app = express(); const schema = buildSchema(` + """ + Representing a tile with it's possible pathways. + """ type MazeTile { id: String! paths: [Int!]! @@ -15,13 +19,18 @@ type Query { currentPostition(userID: String!): MazeTile + "Lists currently available maps." + listMaps: [String!]! } `); const games = new GameServer(); +const maps = createSampleMaps(); +maps.forEach(m => games.addMap(m)); const root = { currentPostition: ({userID}) => games.currentPosition(userID), + listMaps: () => games.listMaps(), }; app.use(apiRoot, graphqlHTTP({