Newer
Older
Labyrinth / src / GameServer.ts
import {
    Direction,
    DirectionToVector,
    MazeMap,
    MazeTile,
} from "./MazeMap";

class GameState
{
    public userID: string;
    public mapID: string;
    public tileID: string;

    constructor(userID: string, mapID: string, tileID: string)
    {
        this.userID = userID;
        this.mapID = mapID;
        this.tileID = tileID;
    }
}

export class GameServer
{
    // By keeping mapList private, we can ensure,
    // that the server contains only valid maps.
    private mapList: MazeMap[];
    private states: GameState[];

    constructor()
    {
        this.mapList = [];
        this.states = [];
    }

    public addMap(newMap: MazeMap): boolean
    {
        const validationResult = newMap.validate();
        if(validationResult.length > 0){
            let message = "Cannot add invalid map:";
            for (const e of validationResult) {
                message += "\n\t" + e;
            }
            throw new Error(message);
        }

        if(-1 === this.mapList.findIndex(m => m.id === newMap.id))
        {
            this.mapList.push(newMap);
            return true;
        }
        else {
            return false;
        }
    }

    public startGame(userID: string, mapID: string): boolean
    {
        this.userIdGuard(userID);
        const map = this.mapList.find(m => m.id === mapID);
        if(map == null){
            throw new Error(`Map "${mapID}" does not exist.`);
        }
        const start = map.getStartTile();

        let state = this.states.find(s => s.userID === userID);
        if(state == null)
        {
            state = new GameState(userID, map.id, start.id);
            this.states.push(state);
        }
        else
        {
            state.mapID = map.id;
            state.tileID = start.id;
        }

        return true;
    }

    public endGame(userID: string): boolean
    {
        const stateIndex = this.states.findIndex(s => s.userID === userID);
        if(stateIndex !== -1)
        {
            this.states.splice(stateIndex, 1);
        }
        return true;
    }

    public navigate(userID: string, direction:Direction): MazeTile
    {
        this.userIdGuard(userID);

        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
    {
        this.userIdGuard(userID);

        const state = this.states.find(s => s.userID === userID);
        if(state == null){
            return null;
        }

        const map = this.mapList.find(m => m.id === state.mapID);
        const tile = map.getTile(state.tileID);
        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.
     * @param userID supplied userID
     */
    private userIdGuard(userID: string): void
    {
        if(userID === undefined || userID === null){
            throw new Error("UserID must be supplied!");
        }
        if(userID === "" || /^\s+$/.test(userID)){
            throw new Error("UserID must not be empty.");
        }
    }
}