Programmerare, skeptiker, sekulärhumanist, antirasist.
Författare till bok om C64 och senbliven lantis.
Röstar pirat.
2017-01-05
Jag har börjat arbeta med ett datorspel i kategorin roguelike. Varje spel ska vara unikt, så kartan genereras när en användare spelar spelet. Vid uppstart skapar jag en labyrint som sen används som kontur för rummen som genereras efter behov. De rum som spelaren inte besöker behöver man trots allt inte hålla i RAM, men jag vill ha konturen färdig för att kunna ha lite kontroll över kartans kvalité.
Vidare måste cellen kunna svara på var i labyrinten den befinner sig. Det behövs för att cellens grannar ska kunna hittas. En cell som inte kan svara på det, får inte skapas över huvudet taget.
public int X { get; }
public int Y { get; }
public Cell(int x, int y)
{
X = x;
Y = y;
}
Eftersom cellen enligt algoritmens regler måste kunna svara på hur många väggar den har, måste vi beskriva hur vi kan veta det.
public int WallsCount
{
get
{
var c = WallNorth ? 1 : 0;
c += WallEast ? 1 : 0;
c += WallSouth ? 1 : 0;
c += WallWest ? 1 : 0;
return c;
}
}
Och slutligen måste cellen kunna berätta om sin position i ett lämpligt format.
public Point Position => new Point(X, Y);
Därefter behövs en representation av själva labyrinten som kan hålla en matris av celler. Denna gång väljer jag att skapa en liten labyrint med 100 celler (10 x 10). Bredden och höjden måste kunna läsas (get) under programmets gång. Koden skapar inte cellerna, endast platshållaren för dem.
public class Labyrinth
{
public static int Width { get; } = 10;
public static int Height { get; } = 10;
public Cell[,] Cell = new Cell[Width, Height];
}
Sen ska vi beskriva algoritmen i labyrintens konstruktor (=funktion med ansvar för initiering). Vi börjar med att berätta att vi vill kunna skapa slumptal, och att platshållaren för våra celler verkligen ska innehålla celler.
var rnd = new Random();
for (var y = 0; y < Height; y++)
for (var x = 0; x < Width; x++)
Cells[x, y] = new Cell(x, y);
Därefter måste vi definiera konceptet med grannar för konstruktorn. Grannar är angränsande celler.
Func<int, int, List<Point>> getNeighbours = (x, y) =>
{
var ret = new List<Point>();
if (y > 0 && Cells[x, y – 1].WallsCount >= 4)
ret.Add(Cells[x, y – 1].Position);
if (x < Width – 1 && Cells[x + 1, y].WallsCount >= 4)
ret.Add(Cells[x + 1, y].Position);
if (y < Height – 1 && Cells[x, y + 1].WallsCount >= 4)
ret.Add(Cells[x, y + 1].Position);
if (x > 0 && Cells[x – 1, y].WallsCount >= 4)
ret.Add(Cells[x – 1, y].Position);
return ret;
};
Sen måste vi beskriva hur man slår in en vägg. Det är inte så enkelt som att sätta en cells vägg-egenskap (t.ex. WallNorth) till false, eftersom grannen åt det hållet också måste få sin motsvarande vägg (t.ex. WallSouth) satt till false.
Action<Cell, Point> knockWall = (cell1, cell2) =>
{
if (cell2.Y < cell1.Y)
{
cell1.WallNorth = false;
Cells[cell2.X, cell2.Y].WallSouth = false;
return;
}
if (cell2.X > cell1.X)
{
cell1.WallEast = false;
Cells[cell2.X, cell2.Y].WallWest = false;
return;
}
if (cell2.Y > cell1.Y)
{
cell1.WallSouth = false;
Cells[cell2.X, cell2.Y].WallNorth = false;
return;
}
if (cell2.X < cell1.X)
{
cell1.WallWest = false;
Cells[cell2.X, cell2.Y].WallEast = false;
return;
}
};
Nu har konstruktorn tillgång till resurserna, och den vet vad en granne är och hur man slår in en vägg. Dags att sätta igång! Vi behöver hålla koll på vilka celler vi besökt…
var queue = new Queue<Cell>();
…och så behöver vi en slumpvis utvald cell att börja i.
var c = Cells[rnd.Next(Width), rnd.Next(Height)];
queue.Enqueue(c);
Därefter, slå ut en vägg och följ efter. Om det inte går, backa till start och prova igen.
while (queue.Count > 0)
{
var neighbours = getNeighbours(c.X, c.Y);
if (neighbours.Count > 0)
{
var neighbour = neighbours[rnd.Next(neighbours.Count)];
knockWall(c, neighbour);
queue.Enqueue(c);
c = Cells[neighbour.X, neighbour.Y];
}
else
{
c = queue.Dequeue();
}
}
Kategorier: Uncategorized
Bjud mig på en kopp kaffe (20:-) som tack för bra innehåll!
Lämna ett svar