Jak działa generator map
Na prośbę jednego z graczy, i może też dlatego że jest to jeden z tych aspektów EFa którym zawsze lubiłem się zajmować, mały wpis poświęcony działaniu generatora map losowych (RMG).
Generator ma za zadanie utworzyć mapę, która nosić ma znamiona losowości, jednakże musi być sprawiedliwa dla wszystkich grających najbardziej jak to tylko możliwe. Mapy losowe występują w kilku odsłonach tematycznych, jest to nie mniej różnica tylko w wyglądzie tła i rodzaju przeszkód. W pierwszej kolejności generowane są różnorakie parametry mapy skorelowane z ilością graczy na jaką ma zostać ona przygotowana, np.: początkowy wygląd państwa, ilość złóż złota, ilość przeszkód terenowych, rozrzut złóż, itp.
Kolejnym krokiem jest rozmieszczenie graczy na mapie z uwzględnieniem pewnego rozrzutu, im więcej graczy tym jest on mniejszy aby mogli się oni w ogóle pomieścić w obszarze gry. W promieniu danego gracza umieszczane też są złoża aby mieć pewność że każdy z grających ma jakieś blisko siebie. Nie mniej jeśli pechowo trafi mogą one i tak być względnie daleko. Kolejno rozmieszczane są pozostałe „kropki” w losowych miejscach mapy. Na koniec ustawiane są ozdoby terenu z zachowaniem 2 sektorowego obramowania wokoło mapy – swego czasu zdarzało się że gracza wyrzucało w róg a dookoła tworzył się mur z przeszkód, co oczywiście uniemożliwiało rozstrzygnięcie partii. I gotowe.
Dla zainteresowanych, kompletny kod źródłowy odpowiedzialny za RMG (Delphi):
procedure RandomEFMapServerSide(const players: Byte; const theme: Byte; var fields: TEFMapServerSide);
function FindBuild(const x, y: Integer; const r: Single; const fields: TEFMapServerSide): Boolean;
var
i, u: Integer;
begin
Result := False;
for i := x - Ceil(r) to x + Ceil(r) do
for u := y - Ceil(r) to y + Ceil(r) do
if ValidCoords(i, u) then
if (Power(i - x, 2) + Power(u - y, 2) <= Power(r, 2)) and
((fields[i, u].owner <> 0) or (fields[i, u].build <> 0)) then
begin
Result := True;
Break;
end;
end;
const
start_fields: array[0..7, 0..2, 0..2] of Byte = ( // 0 - puste, 1 - gracz, 2 - ratusz, 3 - zloze
(
(0,1,0),
(1,2,1),
(0,1,0)
),
(
(0,1,0),
(0,2,0),
(0,1,0)
),
(
(0,0,0),
(1,2,1),
(0,0,0)
),
(
(0,0,0),
(0,2,0),
(0,0,0)
),
(
(1,1,1),
(1,2,1),
(1,1,1)
),
(
(0,0,0),
(0,2,1),
(0,1,1)
),
(
(1,1,0),
(1,2,1),
(0,1,1)
),
(
(1,1,1),
(1,2,1),
(0,0,0)
)
);
var
i, u: Integer;
x, y, r, p, m: Byte;
mp_startfield, mp_minecount, mp_minecountperplayer, mp_terraincount, mp_playerscount, mp_radius, mp_mineradius: Integer;
begin
// Generowanie parametrow
mp_startfield := Random(Length(start_fields));
mp_minecount := Random(3)+4;
mp_minecountperplayer := Ceil((Random(8+1)+12) / players);
mp_terraincount := Random(7) + Round((MaxPlayers-players+Random(4))*1.25);
mp_playerscount := players;
mp_radius := Min(MaxPlayers-players+4, 10);
mp_mineradius := Max(Round(6*(1-((players-2)/MaxPlayers))), 2);
// Czyszczenie mapy
for x := 0 to AreaWidth-1 do
for y := 0 to AreaHeight-1 do
begin
fields[x, y].owner := 0;
fields[x, y].build := 0;
fields[x, y].army := 0;
fields[x, y].mine := 0;
end;
// Ustawianie graczy
p := 0;
r := mp_radius;
while (mp_playerscount > 0) do
begin
// Losowanie srodka panstwa (3x3)
x := Random(AreaWidth-2)+1;
y := Random(AreaHeight-2)+1;
if (fields[x, y].build = 0) and (not FindBuild(x, y, r, fields)) then
begin
for i := 0 to 2 do
for u := 0 to 2 do
begin
if start_fields[mp_startfield, i, u] >= 1 then fields[u+x-1, i+y-1].owner := players-mp_playerscount+1;
if start_fields[mp_startfield, i, u] = 2 then
begin
fields[u+x-1, i+y-1].build := 1;
fields[u+x-1, i+y-1].army := Armors[BuildIDToAct(fields[u+x-1, i+y-1].build)];
end
else if start_fields[mp_startfield, i, u] = 3 then
fields[u+x-1, i+y-1].mine := 1;
end;
Dec(mp_playerscount);
p := 0;
r := mp_radius;
end;
if p = 32 then
begin
p := 0;
if r = 2 then Break else Dec(r);
end
else
Inc(p);
end;
// Ustawianie zloz wokol graczy
for x := 0 to AreaWidth-1 do
for y := 0 to AreaHeight-1 do
if fields[x, y].build = 1 then
begin
m := mp_minecountperplayer;
while m > 0 do
begin
i := Random(mp_mineradius*2+1)-mp_mineradius+x;
u := Random(mp_mineradius*2+1)-mp_mineradius+y;
if ValidCoords(i, u) then
if (fields[i, u].build = 0) and (fields[i, u].mine = 0) then
if ((not ValidCoords(i+1, u)) or (fields[i+1, u].mine = 0)) and
((not ValidCoords(i-1, u)) or (fields[i-1, u].mine = 0)) and
((not ValidCoords(i, u+1)) or (fields[i, u+1].mine = 0)) and
((not ValidCoords(i, u-1)) or (fields[i, u-1].mine = 0)) then
begin
fields[i, u].mine := 1;
Dec(m);
end;
end;
end;
// Ustawianie pozostalych zloz
while mp_minecount > 0 do
begin
x := Random(AreaWidth);
y := Random(AreaHeight);
if (fields[x, y].owner = 0) and (fields[x, y].build = 0) and (fields[x, y].mine = 0) then
if ((not ValidCoords(x+1, y)) or (fields[x+1, y].mine = 0)) and
((not ValidCoords(x-1, y)) or (fields[x-1, y].mine = 0)) and
((not ValidCoords(x, y+1)) or (fields[x, y+1].mine = 0)) and
((not ValidCoords(x, y-1)) or (fields[x, y-1].mine = 0)) then
begin
fields[x, y].mine := 1;
Dec(mp_minecount);
end;
end;
// Ustawianie ozdob terenu
while mp_terraincount > 0 do
begin
x := Random(AreaWidth-4)+2;
y := Random(AreaHeight-4)+2;
if (fields[x, y].owner = 0) and (fields[x, y].build = 0) and (fields[x, y].mine = 0) then
begin
fields[x, y].build := RMGThemes[theme, Random(RMGThemes[theme, 0])+1];
Dec(mp_terraincount);
end;
end;
end;