[C#] Algorytm generowania mapy/prowincji

W niedługim czasie mam zamiar zaprezentować algorytmy wykorzystywane w moich programach. Część w nich przedstawię w całości, opisując ich działanie; natomiast resztę umieszczę jako pliki z klasami i sposobem ich użycia.

Zacznę tutaj od algorytmu tworzenia terenu. Który powstał już dawno temu i bardzo dobrze spełniał swoje zadanie. Przygotowałem go do tworzenia mapy dla gry strategicznej w stylu general. Gdzie podstawowym założeniem było różne umiejscowienie państw i wody...

Założeniem algorytmu było podzielenie mapy na ameby(prowincje).
Mapa była reprezentowana przez tablice kwadratową klasy Pole. A ameby/prowincje zawierały w sobie ich listę. Z tej listy można było utworzyć baseny wodne a także początkowe pozycje graczy - nie mniej z tych dodatkowych rzeczy poruszymy tylko zagadnienie tworzenia obszarów wodnych z mniejszych prowincji. Tak by wszystkie prowincje przedstawiały mniej więcej podobną wielkość.

Klasy użyte w algorytmie można znaleźć w tym pliku. Znajduje się tam także algorytm i jego robocza wersja czyli przed moim opracowaniem dla tego postu.

Tutaj umieszczę kod algorytmu a pod nim kod klas wykorzystywanych w nim:
Algorytm:



// Główna funkcja generująca mapę
        public void GenerujNowąMape( int ileGraczy)
        {  
            //zmienna ustala liczbę prowincji
            const int liczbaProwincji = 2300;          
          
            Random random = new Random();
            Vertices = new List<Triangulator.Geometry.Point>();
           // lista ameb
            List<AMEBA> ameby = new List<AMEBA>();           
            map.pole = new Pole[mapX, mapY];

            for(int x = 0; x < mapX; x++)
            {
                for(int y = 0; y < mapY; y++)
                {
                    map.pole[x,y] = new Pole();                   
                }
            }
          
            for (int x = 0; x < liczbaProwincji; ) // optimum koło 500
            {              
                bool losój = true;
                Triangulator.Geometry.Point pNew;
                while (true)
                {                  
                    pNew = new Triangulator.Geometry.Point(random.Next(0, mapX), random.Next(0, mapY)); // losujemy pozycje kolejnej ameby
                    losój = false; // przyjmujemy że punkt się nie powtarza
                    foreach (Triangulator.Geometry.Point vertices in Vertices) // ale i tak to sprawdzamy:)
                    {
                        if (vertices.X == pNew.X && vertices.Y == pNew.Y) // jeśli już jest w tym miejscu jakaś inna ameba
                        {                           
                            losój = true; // ustaw na dalsze losowanie
                            break; // wyjdz z tej pętli
                        }                       
                    }
                    //Jesli losój jest na true to znaczy że dany punkt się dubluje - losujemy kolejny raz jeśli nie kończymy
                    if (!losój) // jesli się punkty nie powtarzają to zrób tak
                    {
                        Vertices.Add(pNew);
                        ameby.Add(new AMEBA(x, pNew));
                        break; // zakończ losowanie
                    }
                   
                }              
               
                x++;   
            }

            int LiczbaNieaktywnychAmeb = 0;
            while(LiczbaNieaktywnychAmeb != ameby.Count)
            {
              
            //dla każdej z ameb po jednym ruchu
                for (int x = 0; x < ameby.Count; x++)
                {                  
                  
                    if (ameby[x].AKTYWNA)
                    {
                        
                        bool RuchWykonany = false;
                        //ameby szukają w pierwszej kolejności wolnych przestrzeni
                        for (int y = 0; y < ameby[x].pozycje.Count && !RuchWykonany; y++)
                        {
                           
                            //lewo
                            if (ameby[x].pozycje[y].X - 1 >= 0 && map.pole[(int)(ameby[x].pozycje[y].X - 1), (int)(ameby[x].pozycje[y].Y)].Typ != Typ_Pola.Ziemia && !RuchWykonany)
                            {
                                ameby[x].pozycje.Add(new Triangulator.Geometry.Point((int)(ameby[x].pozycje[y].X - 1), (int)(ameby[x].pozycje[y].Y))); //dodajemy pozycje ameby
                                ameby[x].ZwiększRozmiarAmeby();
                                map.pole[(int)(ameby[x].pozycje[y].X - 1), (int)(ameby[x].pozycje[y].Y)].Typ = Typ_Pola.Ziemia;
                             

                                RuchWykonany = true;
                            }

                            //prawo
                            if (ameby[x].pozycje[y].X + 1 < mapX && map.pole[(int)(ameby[x].pozycje[y].X + 1), (int)(ameby[x].pozycje[y].Y)].Typ != Typ_Pola.Ziemia && !RuchWykonany)
                            {
                                ameby[x].pozycje.Add(new Triangulator.Geometry.Point((int)(ameby[x].pozycje[y].X + 1), (int)(ameby[x].pozycje[y].Y))); //dodajemy pozycje ameby
                                ameby[x].ZwiększRozmiarAmeby();
                                map.pole[(int)(ameby[x].pozycje[y].X + 1), (int)(ameby[x].pozycje[y].Y)].Typ = Typ_Pola.Ziemia;
                              
                                RuchWykonany = true;
                            }

                            //góra
                            if (ameby[x].pozycje[y].Y - 1 >= 0 && map.pole[(int)(ameby[x].pozycje[y].X), (int)(ameby[x].pozycje[y].Y) - 1].Typ != Typ_Pola.Ziemia && !RuchWykonany)
                            {
                                ameby[x].pozycje.Add(new Triangulator.Geometry.Point((int)(ameby[x].pozycje[y].X), (int)(ameby[x].pozycje[y].Y - 1))); //dodajemy pozycje ameby
                                ameby[x].ZwiększRozmiarAmeby();
                                map.pole[(int)(ameby[x].pozycje[y].X), (int)(ameby[x].pozycje[y].Y - 1)].Typ = Typ_Pola.Ziemia;
                               

                                RuchWykonany = true;
                            }

                            //dół
                            if (ameby[x].pozycje[y].Y + 1 < mapY && map.pole[(int)(ameby[x].pozycje[y].X), (int)(ameby[x].pozycje[y].Y) + 1].Typ != Typ_Pola.Ziemia && !RuchWykonany)
                            {
                                ameby[x].pozycje.Add(new Triangulator.Geometry.Point((int)(ameby[x].pozycje[y].X), (int)(ameby[x].pozycje[y].Y + 1))); //dodajemy pozycje ameby
                                ameby[x].ZwiększRozmiarAmeby();
                                map.pole[(int)(ameby[x].pozycje[y].X), (int)(ameby[x].pozycje[y].Y) + 1].Typ = Typ_Pola.Ziemia;
                               


                                RuchWykonany = true;
                            }

                            //Jeśli ameba nadal może się ruszać, czyli nie rozmnożyła się na pole boczne bo nie ma już wolnego koło niej to ją wyłącz
                            if (RuchWykonany == false && y + 1 == ameby[x].pozycje.Count)
                            {
                                ameby[x].ZdeaktywujAmebe();
                                LiczbaNieaktywnychAmeb++;
                            }
                        }
                    }

                }
/*
                //jeśli jednak nie mają co jeść wgryzają się w inne ameby ale tylko jeśli tamte są większe od nich - narazie bez tego
               
 */               
            }



Klasa AMEBA:



public class AMEBA
        {
            public List<Triangulator.Geometry.Point> pozycje;
            public int WielkośćAmeby;
            public bool AKTYWNA;
            int ID_AMEBY;
            public AMEBA(int ID,Triangulator.Geometry.Point poz)
            {
                ID_AMEBY = ID;
                WielkośćAmeby = 1;
                pozycje = new List<Triangulator.Geometry.Point>();
                pozycje.Add(poz); //dodanie pierwszej pozycji

                AKTYWNA = true;
            }
            public void ZwiększRozmiarAmeby()
            {
                WielkośćAmeby++;
            }
            public int WeźID()
            {
                return ID_AMEBY;
            }
            public void ZdeaktywujAmebe()
            {
                AKTYWNA = false;
            }
        }



Klasa Poin



/// <summary>
       /// 2D Point with double precision
       /// </summary>
       public class Point
       {
             /// <summary>
             /// X component of point
             /// </summary>
             protected double _X;
             /// <summary>
             /// Y component of point
             /// </summary>
             protected double _Y;

             /// <summary>
             /// Initializes a new instance of a point
             /// </summary>
             /// <param name="x"></param>
             /// <param name="y"></param>
             public Point(double x, double y)
             {
                    _X = x;
                    _Y = y;
             }
      
             /// <summary>
             /// Gets or sets the X component of the point
             /// </summary>
             public double X
             {
                    get { return _X; }
                    set { _X = value; }
             }

             /// <summary>
             /// Gets or sets the Y component of the point
             /// </summary>
             public double Y
             {
                    get { return _Y; }
                    set { _Y = value; }
             }

             /// <summary>
             /// Makes a planar checks for if the points is spatially equal to another point.
             /// </summary>
             /// <param name="other">Point to check against</param>
             /// <returns>True if X and Y values are the same</returns>
             public bool Equals2D(Point other)
             {
                    return (X == other.X && Y == other.Y);
             }
       }



Wszystkie powyższe pliki można znaleźć w paczce z tym algorytmem. Link u góry.
Poniżej screeny przedstawiające algorytm w działaniu: