Esempio n. 1
0
def enemyCanMoveTo(destination: tuple, enemy: Enemy, level: Level):
    """
    Check if the destination given can be moved to by a given enemy
    :param destination: tuple
    :param enemy: Enemy
    :param level: Level
    """
    # TODO: not needed yet
    destBoardNumber = whichBoardInLevel(level, destination)
    enemyBoardNumber = whichBoardInLevel(level, enemy.location)
    # TODO: will need to change to enemyPossibleCardinalMoves when specs for
    #  enemy movement come in, for now using player cardinal movement.
    possibleMoves = playerPossibleCardinalMoves(enemy.location, 2,
                                                level, enemyBoardNumber)

    if destination not in possibleMoves:
        return False

    board = level.boards[destBoardNumber]
    for tile in board.tiles:
        if tile.location == destination:
            if tile.tileType is not TileEnum.WALL:  # TODO ghost/zombie
                return True

    return False
Esempio n. 2
0
def playerCanMoveTo(destination: tuple, player: Player, level: Level,
                    numMoves: int):
    """
    Check if the destination given can be moved to by a given player.
    (True if destination does not have a player and is traversable)
    :param destination: tuple
    :param player: Player
    :param level: Level
    :param numMoves: int
    """
    # NOTE this gets all locations in the board
    destBoardNumber = whichBoardInLevel(level, destination)
    playerBoardNumber = whichBoardInLevel(level, player.location)
    possibleMoves = playerPossibleCardinalMoves(player.location, numMoves,
                                                level)
    if destination not in possibleMoves:
        return False

    board = level.boards[destBoardNumber]

    if isTileOnBoard(destination, board):
        if board.tiles[destination[0]][
            destination[1]].tileType is not TileEnum.WALL \
                and not destHasPlayer(destination, board):
            return True
    return False
Esempio n. 3
0
def convertJsonDungeon(jsonLevel: dict, jsonPlayers: list, jsonEnemies: list,
                       jsonExitLocked: bool):
    level: Level = convertJsonLevel(jsonLevel["rooms"], jsonLevel["hallways"],
                                    jsonLevel["objects"])
    level.exitUnlocked = not jsonExitLocked

    playerNames = []
    for player in jsonPlayers:
        # make player
        newPlayer = convertJsonPlayer(player)
        playerNames.append(newPlayer.name)
        playerDict = {newPlayer.name: newPlayer}
        playerBoard = whichBoardInLevel(level, newPlayer.location)
        level.boards[playerBoard] = addPlayersToBoard(
            level.boards[playerBoard], playerDict)
    # Same for enemies
    enemyNames = []
    for enemy in jsonEnemies:
        newEnemy = convertJsonEnemy(enemy)
        enemyNames.append(newEnemy.name)
        enemyDict = {newEnemy.name: newEnemy}
        enemyBoard = whichBoardInLevel(level, newEnemy.location)
        level.boards[enemyBoard] = addEnemiesToBoard(level.boards[enemyBoard],
                                                     enemyDict)

    game = Dungeon([level], playerNames, 0, False)
    return game
Esempio n. 4
0
def move(entityName: str, destination, game: Dungeon, isPlayer=True):
    """
    Updates the game to reflect the movement of a player if they
    can move to the given destination in the game or skip a turn when
    destination is None. Returns an unmodified game if the player cannot
    move based on game rules.
    :param entityName: str
    :param destination: tuple | None
    :param game: Dungeon
    """
    currLevel: Level = game.levels[game.currLevel]

    # Player move is skipped
    if destination is None:
        # only increment player turn
        updatedPlayerTurn = currLevel.playerTurn + 1
        if updatedPlayerTurn == len(game.players) - 1:
            currLevel.playerTurn = 0
        else:
            currLevel.playerTurn = updatedPlayerTurn
        return game

    entity = getPlayer(currLevel, entityName) if isPlayer else getEnemy(
        currLevel, entityName)
    currBoardNum = whichBoardInLevel(currLevel, entity.location)
    newBoardNum = whichBoardInLevel(currLevel, destination)
    numMoves = 2
    if isPlayer and playerCanMoveTo(destination, entity, currLevel, numMoves):
        updatedLevel = moveEntity(currLevel,
                                  entityName,
                                  currBoardNum,
                                  newBoardNum,
                                  destination,
                                  isPlayer=True)
        if updatedLevel.playerTurn == len(game.players) - 1:
            updatedLevel.playerTurn = 0
            updatedLevel.enemyTurn = updatedLevel.enemyTurn + 1
        else:
            updatedLevel.playerTurn = updatedLevel.playerTurn + 1
            updatedLevel.enemyTurn = updatedLevel.enemyTurn + 1
        game.levels[game.currLevel] = updatedLevel
        updatedGame = interact(entityName, destination, game)
        return updatedGame
    elif not isPlayer:
        updatedLevel = moveEntity(currLevel,
                                  entityName,
                                  currBoardNum,
                                  newBoardNum,
                                  destination,
                                  isPlayer=False)
        game.levels[game.currLevel] = updatedLevel
        updatedGame = enemyInteract(entityName, destination, game)
        return updatedGame
    else:
        return game
Esempio n. 5
0
def getActorsAroundPlayer(possMoves: list, game: Dungeon):
    """
    Returns the actors around a player in JSON format.
    :params possMoves: list
    :params game: Dungeon
    """
    acc = []
    for loc in possMoves:
        level: Level = game.levels[game.currLevel]
        boardNum = whichBoardInLevel(level, loc)
        if destHasEnemy(loc, level.boards[boardNum]):
            enemy: Enemy = first_true(level.boards[boardNum].enemies.values(),
                                      pred=lambda enem: enem.location == loc)
            acc.append({
                "type": enemy.enemyType,
                "name": enemy.name,
                "position": loc
            })
        if destHasPlayer(loc, level.boards[boardNum]):
            player: Player = first_true(
                level.boards[boardNum].players.values(),
                pred=lambda playa: playa.location == loc)
            acc.append({
                "type": "player",
                "name": player.name,
                "position": loc
            })
    return acc
Esempio n. 6
0
def move(playerName: str, destination: tuple, game: Dungeon):
    """
    Updates the game to reflect the movement of a player if they
    can move to the given destination in the game. Returns an
    unmodified game if the player cannot move based on game rules.
    :param playerName: str
    :param destination: tuple
    :param game: Dungeon
    """
    currLevel: Level = game.levels[game.currLevel]
    currBoardNum = whichBoardInLevel(currLevel, destination)
    currBoard: Board = currLevel.boards[currBoardNum]
    player = currBoard.players[playerName]
    numMoves = 2
    if playerCanMoveTo(destination, player, currLevel, numMoves):
        updatedLevel = moveEntity(currLevel,
                                  playerName,
                                  currBoardNum,
                                  destination,
                                  isPlayer=True)
        game.levels[game.currLevel] = updatedLevel
        updatedGame = interact(playerName, destination, game)
        return updatedGame
    else:
        return game
Esempio n. 7
0
def enemyPossibleMoves(enemy: Enemy, game: Dungeon):
    """
    Gets a list of the possible moves a given enemy can feasibly make.
    In essence, gets all the moves are traversable within each enemy's
    walkable distance.
    :params enemy: Enemy
    :params game: Dungeon
    """
    possibleMoves = []
    enemyBoardNum = whichBoardInLevel(game.levels[game.currLevel],
                                      enemy.location)
    enemyBoard: Board = game.levels[game.currLevel].boards[enemyBoardNum]
    if enemy.enemyType == "zombie":
        tilesAround = getLocationsAround(enemy.location)
        for loc in tilesAround:
            if locationInBounds(loc, enemyBoard.origin, enemyBoard.dimensions):
                tile = enemyBoard.tiles[loc[0]][loc[1]]
                if tile.tileType != TileEnum.WALL and tile.tileType != TileEnum.DOOR:
                    possibleMoves.append(loc)
    elif enemy.enemyType == "ghost":
        tilesAround = getLocationsAround(enemy.location)
        for loc in tilesAround:
            if locationInLevelBounds(game.levels[game.currLevel], loc):
                possibleMoves.append(loc)
    return possibleMoves
Esempio n. 8
0
def main():
    output = {}
    try:
        inputJson = sys.stdin.read()
        parsedJson = json.loads(inputJson.replace("\n", ""))
        jsonLevel, jsonPoint = parsedJson
        givenPoint = intifyTuple(tuple(jsonPoint))
        rooms = jsonLevel["rooms"]
        hallways = jsonLevel["hallways"]
        objects = jsonLevel["objects"]
        level = convertJsonLevel(rooms, hallways, objects)
        currentRoom = whichBoardInLevel(level, givenPoint)
        if currentRoom != -1:
            outputTraversable = isTraversable(level, currentRoom, givenPoint)
            outputObject = objectOutput(objects, givenPoint)
            outputType = typeOutput(level, currentRoom)
            outputReachable = reachableRoomOrigins(level, currentRoom)
        else:
            outputTraversable = False
            outputObject = None
            outputType = 'void'
            outputReachable = []
        output['traversable'] = outputTraversable
        output['object'] = outputObject
        output['type'] = outputType
        output['reachable'] = outputReachable

        print(json.dumps(output))
    except json.JSONDecodeError:
        print("Malformed input.")
        sys.exit(1)
    except KeyboardInterrupt:
        print("Exiting...")
        sys.exit(0)
Esempio n. 9
0
def enemyNextMove(enemy: Enemy, game: Dungeon):
    """
    Selects the next move for a given enemy according to the predetermined
    move strategies for enemies. If no valid moves are possible,
    then returns the enemy's location.
    """
    enemyBoardNum = whichBoardInLevel(game.levels[game.currLevel],
                                      enemy.location)
    possMoves = enemyPossibleMoves(enemy, game)
    log("possible enemy moves to choose from:", str(possMoves))
    if len(possMoves) == 0:
        return enemy.location

    playersInOurRoom, playersInOurLevel = getPlayersInRoomAndLevel(
        enemyBoardNum, game)

    if len(playersInOurRoom) != 0:
        playerLocs = [player.location for player in playersInOurRoom]
        return enemyStrat(possMoves, playerLocs)
    elif len(playersInOurLevel) != 0:
        playerLocs = [player.location for player in playersInOurLevel]
        return enemyStrat(possMoves, playerLocs)
    else:  # No valid move
        log("shouldn't EVER EZVER EVER get here. green man you must MOVE")
        return enemy.location
Esempio n. 10
0
def validMoveForHarness(destination: tuple, level: Level):
    boardNum = whichBoardInLevel(level, destination)
    if boardNum == -1:
        return False
    if destHasPlayer(destination, level.boards[boardNum]):
        return False
    for tile in level.boards[boardNum].tiles:
        if tile.location == destination:
            return tile.tileType is not TileEnum.WALL
    return False
Esempio n. 11
0
def enemyInteract(enemyName: str, destination: tuple, game: Dungeon):
    currLevel: Level = game.levels[game.currLevel]
    currBoardNum = whichBoardInLevel(currLevel, destination)
    currBoard: Board = currLevel.boards[currBoardNum]
    if destHasPlayer(destination, currBoard):
        return interactWithPlayer(destination, game)
    elif destHasWall(destination, currLevel):
        return interactWithWall(enemyName, destination, game)
    else:
        return game
Esempio n. 12
0
def validMoveForHarness(destination: tuple, level: Level):
    boardNum = whichBoardInLevel(level, destination)
    if boardNum == -1:
        return False
    if destHasPlayer(destination, level.boards[boardNum]):
        return False
    if isTileOnBoard(destination, level.boards[boardNum]):
        tile = level.boards[boardNum].tiles[destination[0]][destination[1]]
        return tile.tileType is not TileEnum.WALL
    return False
Esempio n. 13
0
def interactWithPlayer(dest: tuple, game: Dungeon):
    currLevel: Level = game.levels[game.currLevel]
    currBoardNum = whichBoardInLevel(currLevel, dest)
    currBoard: Board = currLevel.boards[currBoardNum]
    for player in currBoard.players.values():
        if player.location == dest:
            updatedGame = removePlayer(player.name, currBoardNum, game)
            if len(getAllPlayers(currLevel)) == 0:
                updatedGame = endGame(updatedGame)
            return updatedGame
    return game
Esempio n. 14
0
def reachableRoomOrigins(level: Level, currBoardIndex: int):
    reachable = []
    givenRoom: Board = level.boards[currBoardIndex]
    if givenRoom.boardType == BoardEnum.HALLWAY:
        for connection in givenRoom.doorLocations:
            connIndex = whichBoardInLevel(level, connection)
            reachable.append(level.boards[connIndex].origin)
    else:
        for connection in givenRoom.doorLocations:
            for board in level.boards:
                if (board.boardType
                        == BoardEnum.HALLWAY) and (connection
                                                   in board.doorLocations):
                    for loc in board.doorLocations:
                        if loc != connection:
                            connIndex = whichBoardInLevel(level, loc)
                            if level.boards[connIndex].origin not in reachable:
                                reachable.append(
                                    level.boards[connIndex].origin)
    return reachable
Esempio n. 15
0
def getMoveStatus(currLevel: Level, playerName: str, move: dict):
    dest = intifyTuple(move["to"])
    if destHasKey(dest, currLevel):
        return [playerName, move, "Key"]
    elif destHasExit(dest, currLevel):
        return [playerName, move, "Exit"]
    elif destHasEnemy(dest,
                      currLevel.boards[whichBoardInLevel(currLevel, dest)]):
        return [playerName, move, "Eject"]
    else:
        return [playerName, move, "OK"]
Esempio n. 16
0
def addPlayer(player: Player, game: Dungeon):
    """
    Adds a new player to a game
    :param player: Player
    :param game: Dungeon
    """
    currLevel: Level = game.levels[game.currLevel]
    currBoardNum = whichBoardInLevel(currLevel, player.location)
    currBoard: Board = currLevel.boards[currBoardNum]
    updatedBoard = addPlayersToBoard(currBoard, {player.name: player})
    game.levels[game.currLevel] = updatedBoard

    return game
Esempio n. 17
0
def destHasWall(destination: tuple, level: Level):
    """
    Checks if moving to the destination will cause an interaction with a wall
    :param destination: tuple
    :param level: Level
    """
    boardNum = whichBoardInLevel(level, destination)
    if boardNum != -1:
        board = level.boards[boardNum]
        tile = board.tiles[destination[0]][destination[1]]
        if tile.tileType == TileEnum.WALL:
            return True
    return False
Esempio n. 18
0
def getEnemiesAroundPlayer(possMoves: list, game: Dungeon):
    acc = []
    for loc in possMoves:
        level: Level = game.levels[game.currLevel]
        boardNum = whichBoardInLevel(level, loc)
        if destHasEnemy(loc, level.boards[boardNum]):
            enemy: Enemy = first_true(level.boards[boardNum].enemies.values(),
                                      pred=lambda enem: enem.location == loc)
            acc.append({
                "type": enemy.enemyType,
                "name": enemy.name,
                "position": loc
            })
    return acc
Esempio n. 19
0
def getSuccessStatus(destination: tuple, initGame: Dungeon):
    level: Level = initGame.levels[0]
    destBoard = whichBoardInLevel(level, destination)
    # 1. exit
    if level.exitUnlocked and level.exitLocation == destination:
        status = MoveStatus.EXITED
    # 2. ejected
    elif destHasEnemy(destination, level.boards[destBoard]):
        status = MoveStatus.EJECTED
    elif destHasKey(destination, level):
        status = MoveStatus.KEY
    # 3. Success
    else:
        status = MoveStatus.SUCCESS
    return status
Esempio n. 20
0
def getVisibleTiles(player: Player, level: Level):
    pLocRow, pLocCol = player.location
    origin = (pLocRow - 2, pLocCol - 2)
    fieldOfView = {}
    for r in range(5):
        fovRow = {}
        relRow = origin[0] + r
        for c in range(5):
            relCol = origin[1] + c
            relLoc = (relRow, relCol)
            if locationInLevelBounds(level, relLoc):
                boardNum = whichBoardInLevel(level, relLoc)
                tile = level.boards[boardNum].tiles[relRow][relCol]
                fovRow[relCol] = tile
        fieldOfView.update({relRow: fovRow})
    return fieldOfView
Esempio n. 21
0
def interactWithEnemy(playerName: str, location: tuple, game: Dungeon):
    """
    Calculates the consequence of a player interacting with an enemy
    and updates the state of the game.
    :param playerName: str
    :param location: tuple
    :param game: Dungeon
    """
    currLevel: Level = game.levels[game.currLevel]
    currBoardNum = whichBoardInLevel(currLevel, location)
    currBoard: Board = currLevel.boards[currBoardNum]
    for enemyName in currBoard.enemies.keys():
        enemy: Enemy = currBoard.enemies[enemyName]
        if location == enemy.location:  # fight!
            del currBoard.players[playerName]
            break
    return game
Esempio n. 22
0
def interactWithExit(playerName: str, location: tuple, game: Dungeon):
    """
    Computes the resulting game state of a player acquires an exit in the
    level.
    :param playerName: str
    :param location: tuple
    :param game: Dungeon
    """
    for level in game.levels:
        if location == level.exitLocation:
            if level.exitUnlocked:
                currBoardNum = whichBoardInLevel(level, location)
                currBoard: Board = level.boards[currBoardNum]
                if len(currBoard.players.values()) == 1:  # last player
                    # FIXME return advanceLevel(game)
                    return removePlayer(playerName, currBoardNum, game)
                else:
                    return removePlayer(playerName, currBoardNum, game)
    return game
Esempio n. 23
0
def interactWithEnemy(playerName: str, location: tuple, game: Dungeon):
    """
    Calculates the consequence of a player interacting with an enemy
    and updates the state of the game.
    :param playerName: str
    :param location: tuple
    :param game: Dungeon
    """
    currLevel: Level = game.levels[game.currLevel]
    currBoardNum = whichBoardInLevel(currLevel, location)
    currBoard: Board = currLevel.boards[currBoardNum]
    for enemyName in currBoard.enemies.keys():
        enemy: Enemy = currBoard.enemies[enemyName]
        if location == enemy.location:  # fight!
            updatedGame = ejectPlayer(playerName, currBoardNum, game)
            # if last player in level, game over
            if len(getAllPlayers(currLevel)) == 0:
                updatedGame = endGame(updatedGame)
            return updatedGame
    return game
Esempio n. 24
0
def interactWithExit(playerName: str, location: tuple, game: Dungeon):
    """
    Computes the resulting game state of a player acquires an exit in the
    level.
    :param playerName: str
    :param location: tuple
    :param game: Dungeon
    """
    log("Interacting with exit")
    for level in game.levels:
        if location == level.exitLocation:
            if level.exitUnlocked:
                currBoardNum = whichBoardInLevel(level, location)
                log("Removing player")
                updatedGame = exitPlayer(playerName, currBoardNum, game)
                if len(getAllPlayers(level)) == 0:
                    updatedGame = endGame(updatedGame)
                return updatedGame

    return game
Esempio n. 25
0
def interact(playerName: str, location: tuple, game: Dungeon):
    """
    Updates the game to reflect a player's interaction with a location
    in the game.
    :param playerName: str
    :param location: tuple
    :param game: Dungeon
    """
    currLevel: Level = game.levels[game.currLevel]
    currBoardNum = whichBoardInLevel(currLevel, location)
    currBoard: Board = currLevel.boards[currBoardNum]

    if destHasEnemy(location, currBoard):
        return interactWithEnemy(playerName, location, game)
    elif destHasKey(location, currLevel):
        return interactWithKey(location, game)
    elif destHasExit(location, currLevel):
        return interactWithExit(playerName, location, game)
    # elif destHasItem(location, currBoard):
    #     TODO implement interactWithItem
    #     return game
    else:
        return game
Esempio n. 26
0
def interactWithExit(playerName: str, location: tuple, game: Dungeon):
    """
    Computes the resulting game state of a player acquires an exit in the
    level.
    :param playerName: str
    :param location: tuple
    :param game: Dungeon
    """
    for level in game.levels:
        if location == level.exitLocation:
            if level.exitUnlocked:
                currBoardNum = whichBoardInLevel(level, location)
                playersLeft = getPlayersInLevel(level)
                if len(playersLeft) == 1:  # last player
                    # FIXME return advanceLevel(game)
                    return advanceLevel(game)
                else:
                    updatedGame = removePlayer(playerName, currBoardNum, game)
                    if len(getAllPlayers(level)) == 0:
                        updatedGame = endGame(updatedGame)
                    return updatedGame

    return game
Esempio n. 27
0
def start(args):
    log = logInFile("Server.py")

    # Global vars
    JSON_LEVELS = args['jsonLevels']
    NUM_LEVELS = args['numLevels']
    NUM_CLIENTS = args['numClients']
    WAIT = args['wait']
    OBSERVE = args['observe']
    ADDRESS = args['address']
    PORT = args['port']

    # Ready to begin

    CONNS = {}
    SOCK = None

    try:
        SOCK = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        SOCK.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        SOCK.settimeout(WAIT)
        SOCK.bind((ADDRESS, PORT))
        SOCK.listen(NUM_CLIENTS)

        CONNS = registerPlayers(NUM_CLIENTS, SOCK)

        # Setup game (Convert levels, populate players, populate enemies,
        # create dungeon)
        GAME, playerNames = setupGame(list(CONNS.keys()), JSON_LEVELS)

        # Broadcast initial game state
        broadcast(startLevelMsg(1, playerNames), CONNS)

        # Send initial player update msgs
        broadcastPlayerUpdates(CONNS, GAME)

        log("GAME INITIALIZED!")

        # Enter main loop
        while True:

            # Obtain the current level
            currLevel: Level = GAME.levels[GAME.currLevel]

            # Check if the level has been completed
            if isLevelOver(currLevel):
                GAME = advanceLevel(GAME)
                broadcastLevelOver(GAME, CONNS)

            # Check if game is over
            if GAME.isGameOver:
                broadcastGameOver(GAME, CONNS)
                broadcast("disconnect", CONNS)

            # Execute player turns
            for playerName in playerNames:
                try:
                    player = getPlayer(currLevel, playerName)
                    if player:
                        initLoc = player.location
                        playerTurn = GAME.levels[GAME.currLevel].playerTurn
                        MAX_TRIES = 5
                        for tries in range(MAX_TRIES):
                            time.sleep(1)
                            playerMove = executeTurn(CONNS[playerName])
                            GAME = move(playerName, playerMove, GAME)
                            playerAfter = getPlayer(currLevel, playerName)
                            moveMade = not playerAfter or playerTurn != \
                                       GAME.levels[
                                           GAME.currLevel].playerTurn
                            if moveMade:
                                break
                            # last turn and still no valid move made
                            if not moveMade:
                                if tries == (MAX_TRIES - 1):
                                    GAME = move(playerName, None, GAME)
                                else:
                                    sendPlayer(playerName,
                                               "Invalid move. Try again...",
                                               CONNS)

                        broadcastPlayerUpdates(CONNS, GAME)
                except PlayerDisconnect:
                    # TODO broadcast??
                    log("PLAYER DISCONNECTED")
                    player: Player = getPlayer(currLevel, playerName)
                    playerBoardNum = whichBoardInLevel(currLevel,
                                                       player.location)
                    GAME = removePlayer(playerName, playerBoardNum, GAME)
                    # TODO reset playerNames
                    continue

            # Execute enemy turns
            currLevel: Level = GAME.levels[GAME.currLevel]
            for enemy in getEnemiesInLevel(currLevel):
                nextMove = enemyNextMove(enemy, GAME)
                GAME = move(enemy.name, nextMove, GAME, isPlayer=False)
                broadcastPlayerUpdates(CONNS, GAME)
                # for playerName in playerNames:
                # msg = enemy.name + " moved"
                # broadcast(playerUpdateMsg(playerName, GAME, msg), CONNS)

    except json.JSONDecodeError:
        print("Malformed level file. Check your formatting")
        closeConns(CONNS)
        if SOCK:
            SOCK.close()
        sys.exit(1)
    except KeyboardInterrupt:
        print("\nExiting...")
        closeConns(CONNS)
        if SOCK:
            SOCK.close()
        sys.exit(0)
Esempio n. 28
0
def main():
    DEFAULT_LEVEL = './tests/snarl.levels'
    numPlayers = 1
    startLevel = 1
    jsonLevels = []
    isObserving = False
    numLevels = 0

    parser = argparse.ArgumentParser()  # initialize
    # this is how you add an optional argument
    parser.add_argument("--levels",
                        help="Enter the name of an input JSON Level file",
                        nargs='?',
                        const=DEFAULT_LEVEL, type=str)
    parser.add_argument("--players", help="Enter an amount of players 1-4",
                        nargs='?',
                        const=numPlayers, type=int)
    parser.add_argument("--start",
                        nargs='?',
                        help="Enter the number of the level to start the game from",
                        const=startLevel, type=int)
    parser.add_argument("--observe", help="Observe the game",
                        action="store_true")

    # this is called after you define your optional args
    args = parser.parse_args()

    # Parse level
    if args.levels:
        log('got levels flag', args.levels)
        with open(args.levels) as file:
            wholeFile = file.read()
            portions = wholeFile.split('\n\n')
            cleaned = list(filter(lambda port: port != '', portions))
            numLevels = cleaned[0]
            jsonLevels = cleaned[1:]
    else:
        log("using default level")
        with open(DEFAULT_LEVEL) as file:
            wholeFile = file.read()
            portions = wholeFile.split('\n\n')
            cleaned = list(filter(lambda port: port != '', portions))
            numLevels = cleaned[0]
            jsonLevels = cleaned[1:]

    if args.players:
        if 1 <= args.players <= 4:
            log('got players flag', str(args.players))
            numPlayers = args.players
        else:
            print("Players must be from 1-4")
            sys.exit(1)

    if args.start and 0 < args.start <= len(jsonLevels):
        log('got start flag', args.start)
        startLevel = args.start

    if args.observe:
        log('got observe')
        isObserving = True

    try:
        rawJsonGameLevels = jsonLevels[(startLevel - 1):]
        jsonGameLevels = [json.loads(rawJsonLevel) for rawJsonLevel in
                          rawJsonGameLevels]
        log("got+converted {} levels".format(len(jsonGameLevels)))

        # this has all the levels needed with the starting level as the first
        # element in the list
        levels = [convertJsonLevel(jsonLevel["rooms"], jsonLevel["hallways"],
                                   jsonLevel["objects"]) for jsonLevel in
                  jsonGameLevels]

        playerNames = [input("Player {}, enter your name > ".format(i + 1)) for
                       i in
                       range(numPlayers)]

        log("Using levels", str(levels))
        forbidden = [levels[0].keyLocation, levels[0].exitLocation]
        players = []
        playerLocs = []
        i = 0
        while i < numPlayers:  # Populate players in level
            playerName = playerNames[i]
            randBoardNum, randBoard = getRandomRoomInLevel(levels[0])
            log("Rand board", str(randBoardNum), str(randBoard.dimensions))
            loc = genXRandCoords(1, forbidden, randBoard.origin,
                                 randBoard.dimensions).pop()
            if loc not in playerLocs:
                player = Player(playerName, loc)
                players.append(player)
                levels[0].boards[randBoardNum] = addPlayersToBoard(randBoard, {
                    playerName: player})
                playerLocs.append(loc)
                i += 1

        game = Dungeon(levels, playerNames, startLevel - 1, False)

        enemies = []  # LIST of dictionaries for each level

        for i in range(len(game.levels)):
            numZombies = math.floor((i + 1) / 2) + 1
            numGhosts = math.floor(((i + 1) - 1) / 2)
            log("NUM ZOMBIES", str(numZombies), "NUM GHOSTS", str(numGhosts))
            levelEnemies = {}
            enemyLocs = []
            for zombie in range(numZombies):
                randBoardNum, randBoard = getRandomRoomInLevel(levels[i])
                name = "zombie" + str((zombie + 1))
                # log("BUILDIGN NEW ZOMBIEEE")
                # log("NAME: ", name)
                loc = genXRandCoords(1, playerLocs + forbidden + enemyLocs,
                                     randBoard.origin,
                                     randBoard.dimensions).pop()
                log("LOC", str(loc))
                enemyLocs.append(loc)
                newZombie = Enemy(name, loc)
                levelEnemies[name] = (randBoardNum, newZombie)
            for ghost in range(numGhosts):
                randBoardNum, randBoard = getRandomRoomInLevel(levels[i])
                name = "ghost" + str((ghost + 1))
                loc = genXRandCoords(1, playerLocs + forbidden + enemyLocs,
                                     randBoard.origin,
                                     randBoard.dimensions)
                # loc = getRandCoordInLevel(level, True)
                enemyLocs.append(loc)
                newGhost = Enemy(name, loc, "ghost")
                levelEnemies[name] = (randBoardNum, newGhost)

            enemies.append(levelEnemies)
            populateEnemies(i, levelEnemies, game)

        # Initialize screen
        pygame.init()
        clock = pygame.time.Clock()
        screen = pygame.display.set_mode(Globals.SCREEN_DIMENSIONS,
                                         flags=pygame.RESIZABLE)
        pygame.display.set_caption("Local Snarl Demo")

        # Fill background. Draw onto this
        gameDisplaySize = (
            Globals.GAME_DISPLAY_WIDTH, Globals.GAME_DISPLAY_HEIGHT)
        background = pygame.Surface(gameDisplaySize)
        statusBarSize = (Globals.STATUS_BAR_WIDTH, Globals.STATUS_BAR_HEIGHT)
        statusBar = pygame.Surface(statusBarSize)
        background = background.convert()  # converts Surface to single-pixel
        statusBar = statusBar.convert()  # converts Surface to single-pixel
        # format
        background.fill(Globals.BG_COLOR)
        statusBar.fill(Globals.BG_COLOR)

        currLevel0: Level = game.levels[game.currLevel]

        def getEnemies(enemiesDict: list):
            acc = []
            for enemiesInLevel in enemiesDict:
                for enemyName in enemiesInLevel.keys():
                    acc.append(enemiesInLevel[enemyName][1])
            return acc

        allEnemies = getEnemies(enemies)

        log("All enemies", str(allEnemies))
        log("Level has this many boards", str(len(currLevel0.boards)))

        def locationInTiles(location: tuple, tiles: dict):
            lrow, lcol = location
            for row in tiles.keys():
                if lrow == row:
                    for col in tiles[row].keys():
                        if lcol == col:
                            return True
            return False

        # if isObserving:
        #     log("OBSERVER VIEW")
        #     allTiles = getAllTiles(currLevel0)
        #     view = ObserverView("observer", allTiles, [currLevel0.keyLocation],
        #                         [currLevel0.exitLocation],
        #                         players, allEnemies)
        #     renderObserverView(background, view)
        # else:
        #     log("PLAYER VIEW")
        #     player: Player = getPlayer(currLevel0, playerNames[0])
        #     visibleTiles = getVisibleTiles(player, currLevel0)
        #     nearbyEnemies = [enemy for enemy in allEnemies if
        #                      locationInTiles(enemy.location, visibleTiles)]
        #     nearbyPlayers = [player for player in players if
        #                      locationInTiles(player.location, visibleTiles)]
        #     view = PlayerView(playerNames[0],
        #                       visibleTiles,
        #                       player.location,
        #                       [currLevel0.keyLocation],
        #                       [currLevel0.exitLocation],
        #                       nearbyPlayers, nearbyEnemies)
        #     # renderPlayerView(background, view)

        # Block events we don't care about and allow ones we do
        # (speeds processing)
        pygame.event.set_blocked(None)
        pygame.event.set_allowed([QUIT, MOUSEBUTTONDOWN, MOUSEBUTTONUP])

        hasMoved = {}
        for player in game.players:
            hasMoved[player] = 0

        while True:

            # pygame.time.wait(250)
            clock.tick(30)  # cap at 30fps

            currLevel: Level = game.levels[game.currLevel]

            if game.isGameOver:
                log("The game is over! If you won, congrats!")
                sys.exit(0)

            log("Player turn is ", str(currLevel.playerTurn))
            playerName = game.players[currLevel.playerTurn]
            player: Player = getPlayer(currLevel, playerName)

            log("Game players", str(game.players))
            log("Level players", str(getAllPlayers(currLevel)))

            # player not in level (ejected or exited)
            if not player:
                log("Skipping turn")
                hasMoved[playerName] = 1
                log("HASSSMOVVVEEEEDD POST ENEMIESS", str(hasMoved))
                if currLevel.playerTurn == len(game.players) - 1:
                    currLevel.playerTurn = 0
                else:
                    currLevel.playerTurn = currLevel.playerTurn + 1
                # currLevel.playerTurn = currLevel.playerTurn + 1
                # currLevel.enemyTurn = currLevel.enemyTurn + 1
                continue

            log("Player turn", str(currLevel.playerTurn))
            log("Enemy turn:", str(currLevel.enemyTurn))

            # Move all enemies at beginning of rounds (all players moved)
            numPlayersInGame = len(game.players) - 1
            # log("numPlayersInGame", str(numPlayersInGame))
            # if currLevel.enemyTurn == numPlayersInGame:
            if all(hasMoved.values()):
                for enemy in getEnemiesInLevel(currLevel):
                    nextMove = EnemyMove.enemyNextMove(enemy, game)
                    log("Next move is", str(nextMove))
                    GameManager.move(enemy.name, nextMove, game,
                                     isPlayer=False)
                # currLevel.enemyTurn = -1
                for moved in hasMoved.keys():
                    hasMoved[moved] = 0
                log("HASSSMOVVVEEEEDD POST ENEMIESS", str(hasMoved))

            board1: Board = currLevel.boards[
                whichBoardInLevel(currLevel,
                                  player.location)]

            # Render appropriate view
            if isObserving:
                allTiles = getAllTiles(currLevel)
                allPlayers = getAllPlayers(currLevel)
                allEnemies = getAllEnemies(currLevel)
                view = ObserverView("observer", allTiles,
                                    [currLevel.keyLocation],
                                    [currLevel.exitLocation],
                                    allPlayers, allEnemies)
                renderObserverView(background, view)
            else:
                visibleTiles = getVisibleTiles(player, currLevel)
                visiblePlayers = [board1.players[pname] for pname in
                                  board1.players.keys() if
                                  locationInTiles(
                                      # FIXME bug when player next to you
                                      board1.players[pname].location,
                                      visibleTiles)]
                visibleEnemies = [board1.enemies[ename] for ename in
                                  board1.enemies.keys() if
                                  locationInTiles(
                                      board1.enemies[ename].location,
                                      visibleTiles)]
                newView = PlayerView(playerName,
                                     visibleTiles,
                                     player.location,
                                     [currLevel.keyLocation],
                                     [currLevel.exitLocation],
                                     visiblePlayers,
                                     visibleEnemies)
                renderPlayerView(background, newView)

            # Everyone gets a status bar (yay)
            renderStatusBar(statusBar, game)

            # handle user events
            for event in pygame.event.get():
                if event.type == QUIT:
                    sys.exit(0)
                if event.type == MOUSEBUTTONDOWN:  # FIXME and not isObserving:
                    newLoc = translateScreenLocation(event.pos)
                    if not locationInLevelBounds(currLevel, newLoc):
                        continue
                    # log("Got click at", str(event.pos), "--->", str(newLoc))
                    GameManager.move(playerName, newLoc, game)
                    hasMoved[playerName] = 1
                    log("HASSSMOVVVEEEEDD", str(hasMoved))

            """
            gameCollection ---> {gameName: dungeon}
            observerCollection ---> {observerName: gameName}

            when observe requested/game state change
                Observer.send(observerName, gameCollection[observerCollection[observerName]])
            """

            # map of keys pressed

            # keys = pygame.key.get_pressed()
            screen.blit(background,
                        (0, Globals.STATUS_BAR_HEIGHT))  # render pixels to
            screen.blit(statusBar, (0, 0))
            pygame.display.update()  # update


    except FileNotFoundError:
        print("Couldn't find that level file. Try again")
        sys.exit(1)
    except json.JSONDecodeError:
        print("Malformed levels")
        sys.exit(1)
    except KeyboardInterrupt:
        print("\nExiting...")
        sys.exit(0)