public ProtoRegion(int id, ProtoRoom[,] MasterRoomList, REGION_TYPE regionType = REGION_TYPE.OVERGROUND)
        {
            ID = id;
            Name = Statics.RandomRegionName();
            if (regionType == REGION_TYPE.OVERGROUND) { Color = Statics.RandomColor(); }
            else if (regionType == REGION_TYPE.UNDERGROUND) { Color = Statics.RandomCaveColor(); }

            // initialize with a single subregion
            ProtoSubregions.Add(new ProtoSubregion(0, this, MasterRoomList));
        }
        public ProtoSubregion(int id, ProtoRegion region, ProtoRoom[,] MasterRoomList)
        {
            ID = id;
            ProtoRegion = region;
            Color = region.Color;
            Name = Statics.RandomRegionType();

            int RoomCountX = MasterRoomList.GetLength(0);
            int RoomCountY = MasterRoomList.GetLength(1);

            // keep track of available adjacent roomss
            // list for random access; hashset for lookup by coordinates
            List<ProtoRoom> AvailableAdjacentRooms = new List<ProtoRoom>();
            HashSet<PointInt> AvailableAdjacentCoordinates = new HashSet<PointInt>();

            // grab random point as starting room
            int x = Statics.Random.Next(RoomCountX);
            int y = Statics.Random.Next(RoomCountY);
            while (!MasterRoomList[x, y].Available)
            {
                x = Statics.Random.Next(RoomCountX);
                y = Statics.Random.Next(RoomCountY);
            }

            // create starting room
            ProtoRoom startingRoom = MasterRoomList[x, y];
            ProtoRooms.Add(startingRoom);
            startingRoom.Available = false;
            startingRoom.ProtoRegion = region;
            startingRoom.ProtoSubregion = this;
            UpdateAdjacentRooms(AvailableAdjacentRooms, AvailableAdjacentCoordinates, startingRoom, MasterRoomList);

            while (AvailableAdjacentRooms.Count > 0 && ((ProtoRooms.Count < Statics.MinimumRegionSize) || (Statics.Random.Next(100) < Statics.ProbabilityOfExpansion)))
            {
                // pick a random room from available neighbor set
                ProtoRoom randomNeighbor = AvailableAdjacentRooms.RandomListItem();
                randomNeighbor.Available = false;
                randomNeighbor.ProtoRegion = region;
                randomNeighbor.ProtoSubregion = this;

                AvailableAdjacentRooms.Remove(randomNeighbor);
                AvailableAdjacentCoordinates.Remove(randomNeighbor.CoordinatesXY);
                ProtoRooms.Add(randomNeighbor);

                // add new room's available neighbors to the available set
                UpdateAdjacentRooms(AvailableAdjacentRooms, AvailableAdjacentCoordinates, randomNeighbor, MasterRoomList);
            }
        }
        public static Room FromProtoRoom(Region region, Subregion subregion, ProtoRoom pr)
        {
            Room room = new Room();
            room.ID = pr.ID;
            room._coordinatesXY = pr.CoordinatesXY;
            room._region = region;
            room._subregion = subregion;

            // sort room connections
            List<KeyValuePair<string, Tuple<int, int, int>>> DirectionalRoomConnectionsList = pr.DirectionalRoomConnections.ToList();
            DirectionalRoomConnectionsList.Sort((x, y) => Statics.SortRoomConnections(x.Key, y.Key));
            Dictionary<string, Tuple<int, int, int>> DirectionalRoomConnectionsDictionary = DirectionalRoomConnectionsList.ToDictionary((x) => x.Key, (x) => x.Value);

            room.DirectionalRoomConnections = new ReadOnlyDictionary<string, Tuple<int, int, int>>(DirectionalRoomConnectionsDictionary);
            room.RoomConnections = new ReadOnlyCollection<RoomConnection>(pr.ProtoRoomConnections);
            room._elevation = pr.Elevation;
            room._elevationcolor = pr.ElevationColor;

            return room;
        }
 private void UpdateAdjacentRooms(List<ProtoRoom> AvailableAdjacentRooms, HashSet<PointInt> AvailableAdjacentCoordinates, ProtoRoom protoRoom, ProtoRoom[,] MasterRoomList)
 {
     // left
     if (protoRoom.CoordinatesXY.X > 0)
     {
         ProtoRoom neighborLeft = MasterRoomList[protoRoom.CoordinatesXY.X - 1, protoRoom.CoordinatesXY.Y];
         if (neighborLeft.Available && !AvailableAdjacentCoordinates.Contains(neighborLeft.CoordinatesXY))
         {
             AvailableAdjacentRooms.Add(neighborLeft);
             AvailableAdjacentCoordinates.Add(neighborLeft.CoordinatesXY);
         }
     }
     // right
     if (protoRoom.CoordinatesXY.X < MasterRoomList.GetLength(0) - 1)
     {
         ProtoRoom neighborRight = MasterRoomList[protoRoom.CoordinatesXY.X + 1, protoRoom.CoordinatesXY.Y];
         if (neighborRight.Available && !AvailableAdjacentCoordinates.Contains(neighborRight.CoordinatesXY))
         {
             AvailableAdjacentRooms.Add(neighborRight);
             AvailableAdjacentCoordinates.Add(neighborRight.CoordinatesXY);
         }
     }
     // above
     if (protoRoom.CoordinatesXY.Y > 0)
     {
         ProtoRoom neighborAbove = MasterRoomList[protoRoom.CoordinatesXY.X, protoRoom.CoordinatesXY.Y - 1];
         if (neighborAbove.Available && !AvailableAdjacentCoordinates.Contains(neighborAbove.CoordinatesXY))
         {
             AvailableAdjacentRooms.Add(neighborAbove);
             AvailableAdjacentCoordinates.Add(neighborAbove.CoordinatesXY);
         }
     }
     // below
     if (protoRoom.CoordinatesXY.Y < MasterRoomList.GetLength(1) - 1)
     {
         ProtoRoom neighborBelow = MasterRoomList[protoRoom.CoordinatesXY.X, protoRoom.CoordinatesXY.Y + 1];
         if (neighborBelow.Available && !AvailableAdjacentCoordinates.Contains(neighborBelow.CoordinatesXY))
         {
             AvailableAdjacentRooms.Add(neighborBelow);
             AvailableAdjacentCoordinates.Add(neighborBelow.CoordinatesXY);
         }
     }
 }
        private bool HasAvailableNeighboringRoom(ProtoRoom[,] MasterRoomList)
        {
            int RoomCountX = MasterRoomList.GetLength(0);
            int RoomCountY = MasterRoomList.GetLength(1);

            foreach (ProtoRoom room in ProtoRooms)
            {
                // check left
                if (room.CoordinatesXY.X > 0 && MasterRoomList[(int)room.CoordinatesXY.X - 1, (int)room.CoordinatesXY.Y].Available) { return true; }
                // check right
                if (room.CoordinatesXY.X < RoomCountX - 1 && MasterRoomList[(int)room.CoordinatesXY.X + 1, (int)room.CoordinatesXY.Y].Available) { return true; }
                // check up
                if (room.CoordinatesXY.Y > 0 && MasterRoomList[(int)room.CoordinatesXY.X, (int)room.CoordinatesXY.Y - 1].Available) { return true; }
                // check down
                if (room.CoordinatesXY.Y < RoomCountY - 1 && MasterRoomList[(int)room.CoordinatesXY.X, (int)room.CoordinatesXY.Y + 1].Available) { return true; }
            }

            return false;
        }
 private void InitializeMasterRoomLists()
 {
     // initialize master array of tiles
     MasterOvergroundRoomList = new ProtoRoom[Width, Height];
     MasterUndergroundRoomList = new ProtoRoom[Width, Height];
     for (int x = 0; x < Width; x++)
     {
         for (int y = 0; y < Height; y++)
         {
             MasterOvergroundRoomList[x, y] = new ProtoRoom(-1, new PointInt(x, y));
             MasterUndergroundRoomList[x, y] = new ProtoRoom(-1, new PointInt(x, y));
         }
     }
 }
        private void DrawPaths(CanvasDrawingSession ds, ProtoRoom[,] roomList, int x, int y, Color color)
        {
            float fStrokeWidth = 5;

            foreach (string DirectionalRoomConnection in roomList[x, y].DirectionalRoomConnections.Keys)
            {
                switch (DirectionalRoomConnection)
                {
                    case "nw":
                        ds.DrawLine((x + 0.5f) * Statics.MapResolution,
                             (y + 0.5f) * Statics.MapResolution,
                             ((x - 1) + 0.5f) * Statics.MapResolution,
                             ((y - 1) + 0.5f) * Statics.MapResolution,
                             color, fStrokeWidth);
                        break;
                    case "n":
                        ds.DrawLine((x + 0.5f) * Statics.MapResolution,
                             (y + 0.5f) * Statics.MapResolution,
                             (x + 0.5f) * Statics.MapResolution,
                             ((y - 1) + 0.5f) * Statics.MapResolution,
                             color, fStrokeWidth);
                        break;
                    case "ne":
                        ds.DrawLine((x + 0.5f) * Statics.MapResolution,
                             (y + 0.5f) * Statics.MapResolution,
                             ((x + 1) + 0.5f) * Statics.MapResolution,
                             ((y - 1) + 0.5f) * Statics.MapResolution,
                             color, fStrokeWidth);
                        break;
                    case "w":
                        ds.DrawLine((x + 0.5f) * Statics.MapResolution,
                             (y + 0.5f) * Statics.MapResolution,
                             ((x - 1) + 0.5f) * Statics.MapResolution,
                             (y + 0.5f) * Statics.MapResolution,
                             color, fStrokeWidth);
                        break;
                    case "o":
                        break;
                    case "e":
                        ds.DrawLine((x + 0.5f) * Statics.MapResolution,
                             (y + 0.5f) * Statics.MapResolution,
                             ((x + 1) + 0.5f) * Statics.MapResolution,
                             (y + 0.5f) * Statics.MapResolution,
                             color, fStrokeWidth);
                        break;
                    case "sw":
                        ds.DrawLine((x + 0.5f) * Statics.MapResolution,
                             (y + 0.5f) * Statics.MapResolution,
                             ((x - 1) + 0.5f) * Statics.MapResolution,
                             ((y + 1) + 0.5f) * Statics.MapResolution,
                             color, fStrokeWidth);
                        break;
                    case "s":
                        ds.DrawLine((x + 0.5f) * Statics.MapResolution,
                             (y + 0.5f) * Statics.MapResolution,
                             (x + 0.5f) * Statics.MapResolution,
                             ((y + 1) + 0.5f) * Statics.MapResolution,
                             color, fStrokeWidth);
                        break;
                    case "se":
                        ds.DrawLine((x + 0.5f) * Statics.MapResolution,
                             (y + 0.5f) * Statics.MapResolution,
                             ((x + 1) + 0.5f) * Statics.MapResolution,
                             ((y + 1) + 0.5f) * Statics.MapResolution,
                             color, fStrokeWidth);
                        break;
                    default:
                        throw new Exception();
                }
            }
        }
        private ProtoRoom AddRoomConnection(ProtoRoom currentRoom, string strDirection, ProtoRoom[,] masterRoomList, bool bForce = false)
        {
            Tuple<int, int, int> connectionWorldCoordinates;
            if (currentRoom.DirectionalRoomConnections.TryGetValue(strDirection, out connectionWorldCoordinates)) { return currentRoom; }

            string strOppositeDirection = string.Empty;
            PointInt connectingRoomCoordinates = null;
            switch (strDirection)
            {
                case "nw":
                    strOppositeDirection = "se";
                    connectingRoomCoordinates = new PointInt(currentRoom.CoordinatesXY.X - 1, currentRoom.CoordinatesXY.Y - 1);
                    break;
                case "n":
                    strOppositeDirection = "s";
                    connectingRoomCoordinates = new PointInt(currentRoom.CoordinatesXY.X, currentRoom.CoordinatesXY.Y - 1);
                    break;
                case "ne":
                    strOppositeDirection = "sw";
                    connectingRoomCoordinates = new PointInt(currentRoom.CoordinatesXY.X + 1, currentRoom.CoordinatesXY.Y - 1);
                    break;
                case "w":
                    strOppositeDirection = "e";
                    connectingRoomCoordinates = new PointInt(currentRoom.CoordinatesXY.X - 1, currentRoom.CoordinatesXY.Y);
                    break;
                case "e":
                    strOppositeDirection = "w";
                    connectingRoomCoordinates = new PointInt(currentRoom.CoordinatesXY.X + 1, currentRoom.CoordinatesXY.Y);
                    break;
                case "sw":
                    strOppositeDirection = "ne";
                    connectingRoomCoordinates = new PointInt(currentRoom.CoordinatesXY.X - 1, currentRoom.CoordinatesXY.Y + 1);
                    break;
                case "s":
                    strOppositeDirection = "n";
                    connectingRoomCoordinates = new PointInt(currentRoom.CoordinatesXY.X, currentRoom.CoordinatesXY.Y + 1);
                    break;
                case "se":
                    strOppositeDirection = "nw";
                    connectingRoomCoordinates = new PointInt(currentRoom.CoordinatesXY.X + 1, currentRoom.CoordinatesXY.Y + 1);
                    break;
                default:
                    throw new Exception();
            }

            if (connectingRoomCoordinates.X < 0) { return currentRoom; }
            if (connectingRoomCoordinates.X > Width - 1) { return currentRoom; }
            if (connectingRoomCoordinates.Y < 0) { return currentRoom; }
            if (connectingRoomCoordinates.Y > Height - 1) { return currentRoom; }

            ProtoRoom connectingRoom = masterRoomList[connectingRoomCoordinates.X, connectingRoomCoordinates.Y];
            if (connectingRoom.Elevation == 0 || connectingRoom.Elevation == 30) { return currentRoom; }
            if (connectingRoom.HasMaximumConnections && !bForce) { return currentRoom; }

            Debug.TotalConnectionCount++;

            currentRoom.DirectionalRoomConnections.Add(strDirection, connectingRoom.WorldCoordinatesAsTuple);
            connectingRoom.DirectionalRoomConnections.Add(strOppositeDirection, currentRoom.WorldCoordinatesAsTuple);
            return connectingRoom;
        }
        private void MergeRegions(ProtoRoom[,] MasterRoomList, int nMinimumSize, bool bAddAsSubregions)
        {
            for (int i = ProtoRegions.Count - 1; i >= 0; i--)
            {
                if (ProtoRegions[i].RoomCount <= nMinimumSize)
                {
                    // pick random tile and check neighboring tiles for a new region
                    int nMergeRegion = RandomNeighbor(i).ID;

                    // fold ProtoRegions[i] into ProtoRegions[nMergeRegion]
                    MergeRegions(nMergeRegion, i, bAddAsSubregions);

                    // reindex regions, starting at the index where we removed the last one
                    // NOTE: huge savings over reindexing everything every time
                    ReindexRegions(i);
                }
            }
        }