/// <summary>
        /// Verifies whether room placement is possible.
        /// We go through every tile in the room and ensure that it's not overlapping something invalid
        /// (ie: if we try to place a wall that would overlap a floor tile, that's invalid).
        /// </summary>
        /// <param name="roomData"></param>
        /// <param name="mapExit"></param>
        /// <param name="roomExit"></param>
        /// <param name="scope"></param>
        /// <param name="roomPlacementOptions"></param>
        /// <returns></returns>
        private bool RoomHasInvalidCollision(RoomData roomData, Vector2Int mapExit, Vector2Int roomExit, int scope, RoomPlacementOptions roomPlacementOptions = RoomPlacementOptions.None)
        {
            bool fullOverlap = true;

            for (int roomX = 0; roomX < roomData.width; ++roomX)
            {
                for (int roomY = 0; roomY < roomData.height; ++roomY)
                {
                    int        mapX   = mapExit.x - roomExit.x + roomX;
                    int        mapY   = mapExit.y - roomExit.y + roomY;
                    Vector2Int mapPos = new Vector2Int(mapX, mapY);

                    int roomTile = roomData.Tile(roomX, roomY);

                    if (!mRoomScratch.ContainsKey(mapPos) || roomTile == RandomDungeonTileData.EMPTY_TILE)
                    {
                        // Any kind of tile can be placed over an empty location
                        // However, at this point we do know that the rooms are not 100% overlapping
                        fullOverlap = false;
                        continue;
                    }
                    else
                    {
                        int mapTile      = mRoomScratch[mapPos].tileType;
                        int mapTileScope = mRoomScratch[mapPos].scope;

                        if (scope != mapTileScope && !RoomPlacementOptionSet(roomPlacementOptions, RoomPlacementOptions.AllowMismatchScopes))
                        {
                            // We're trying to connect with a room in a different scope, which we can never do to ensure
                            // that a scope only has a single point of entry.
                            return(true);
                        }
                        else if (RoomPlacementOptionSet(roomPlacementOptions, RoomPlacementOptions.AllowWallsToBecomeExits) &&
                                 mapTile == RandomDungeonTileData.WALL_TILE && roomTile == RandomDungeonTileData.EXIT_TILE)
                        {
                            // If the options indicate that walls can become exits, support that.
                            continue;
                        }
                        else if (mapTile != roomTile)
                        {
                            // Only identical tiles can be placed over one another.
                            return(true);
                        }
                    }
                }
            }

            // There's still the chance that this room is being placed completely over a previous room, which
            // we don't want to allow because it throws off our room counts.
            // We should be trying to place against at least one empty square

            return(fullOverlap);
        }
        /// <summary>
        /// Actually performs room placement in the scratchpad.
        /// </summary>
        /// <param name="roomData"></param>
        /// <param name="mapExit"></param>
        /// <param name="roomExit"></param>
        /// <param name="scope"></param>
        /// <param name="primaryPath"></param>
        private void PlaceRoom(RoomData roomData, Vector2Int mapExit, Vector2Int roomExit, int scope, bool primaryPath)
        {
            int maxDistance = 0;

            roomData.placeCount++;

            for (int x = 0; x < roomData.width; ++x)
            {
                for (int y = 0; y < roomData.height; ++y)
                {
                    int        mapX     = mapExit.x - roomExit.x + x;
                    int        mapY     = mapExit.y - roomExit.y + y;
                    Vector2Int mapPos   = new Vector2Int(mapX, mapY);
                    char       roomTile = roomData.Tile(x, y);

                    // Empty tiles shouldn't be represented in the dictionary
                    if (roomTile == RandomDungeonTileData.EMPTY_TILE)
                    {
                        continue;
                    }

                    // Create a new tile or edit a tile that already exists.
                    // This ensures we don't generate unnecessary garbage.
                    RandomDungeonTileData td = null;
                    if (!mRoomScratch.ContainsKey(mapPos))
                    {
                        td = new RandomDungeonTileData();
                        mRoomScratch.Add(mapPos, td);

                        float distance    = Vector2Int.Distance(roomExit, new Vector2Int(x, y));
                        int   distanceInt = Mathf.CeilToInt(distance);
                        maxDistance = Mathf.Max(maxDistance, distanceInt);

                        td.tileId = mCurrentTileId + distanceInt;

                        td.position = mapPos;
                    }
                    else
                    {
                        td = mRoomScratch[mapPos];
                        td.Clear();
                    }

                    td.tileType = roomTile;
                    td.scope    = scope;
                    td.room     = mCurrentRoomId;

                    if (primaryPath)
                    {
                        td.distanceFromPrimaryPath = 0;
                    }
                    else
                    {
                        // todo bdsowers - this is not perfect
                        // maybe it doesn't need to be perfect, we'll see
                        int mapExitDistance = mRoomScratch[mapExit].distanceFromPrimaryPath;
                        td.distanceFromPrimaryPath = mapExitDistance + 1;
                        maxDistanceFromPrimaryPath = Mathf.Max(maxDistanceFromPrimaryPath, td.distanceFromPrimaryPath);
                    }

                    // Trap
                    // Register it in the map proper as a walkable tile
                    // give a block of trap tiles the same identifier - they form one large trap
                    if (roomTile == RandomDungeonTileData.TRAP_TILE)
                    {
                        int neighborTrap = NeighborTrap(mapX, mapY);
                        int trapId       = neighborTrap;
                        if (trapId == -1)
                        {
                            trapId = mCurrentTrapId;
                            mCurrentTrapId++;
                        }

                        td.tileType = RandomDungeonTileData.WALKABLE_TILE;
                        td.trap     = trapId;
                    }
                    else if (roomTile == RandomDungeonTileData.POSSIBLE_CHEST_TILE)
                    {
                        td.tileType = RandomDungeonTileData.WALKABLE_TILE;
                        td.chest    = 1;
                    }
                    else if (roomTile == RandomDungeonTileData.GUARANTEED_CHEST_TILE)
                    {
                        td.tileType = RandomDungeonTileData.WALKABLE_TILE;
                        td.chest    = 2;
                    }

                    // Add the new exit to the list of available exits
                    if (roomTile == RandomDungeonTileData.EXIT_TILE)
                    {
                        mAvailableExits.Add(mapPos);
                    }
                }
            }

            ++mCurrentRoomId;
            mCurrentTileId += maxDistance + 1;
        }