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;