{***********************************************************

Project:    C-evo External Map Generator
Copyright:  1999-2023 P Blackman
License:    GPLv3+

City Start Positions & Deadlands
Deadlands should be within a viable city site

***********************************************************}


function tMap.citysquares : Integer;
begin
    result := Citylist.Count;
end;


// caller must ensure Ht stays within valid range!
procedure tMap.GetCityNeighbour (W, H, N : Integer; out Wt, Ht : Integer);
var O : Integer;
begin
    Wt := W;
    Ht := H;
    O := Integer (not odd (H)); { Even rows need an offset }

    { Get Co-Ordinates for this Neighbour }
    case N of
    0: ;   { my city square }
    1,2:
        begin
            Ht := H - 3;
            Wt := W - 2 + N + O;
        end;
    3,4,5:
        begin
            Ht := H - 2;
            Wt := W - 4 + N;
        end;
    6,7,8,9:
        begin
            Ht := H - 1;
            Wt := W - 8 + N + O;
        end;
    10,11:
        begin
            Ht := H;
            Wt := W + 2*(11-N) -1;  {-1 or +1}
        end;
    12,13,14,15:
        begin
            Ht := H + 1;
            Wt := W - 14 + N + O;
        end;
    16,17,18:
        begin
            Ht := H + 2;
            Wt := W - 17 + N;
        end;
    19,20:  begin
            Ht := H + 3;
            Wt := W - 20 + N + O;
        end;
    end;

    Wt := WrapCheck (Wt);
end;


procedure tMap.DumpCity (W, H: Integer);
var N, Wt, Ht : Integer;
begin
    for N := 0 to 20 do
    begin
        GetCityNeighbour (W, H, N, Wt, Ht);
        Assert ( (Wt > 0) and (Wt <= Width),  'Invalid Width ' +IntToStr (Wt));
        Assert ( (Ht > 0) and (Ht <= Height), 'Invalid Height '+IntToStr (Ht));

        Write (TerrainChar (Getterrain (Wt,Ht)));
    end;
end;


function tMap.CoastalCity (const W,H : Integer) : Boolean;
var T,Wn,Hn : Integer;
    Landlocked : Boolean;
begin
    LandLocked := True;
    T := 0;
    while landlocked and (T < 8) do
    begin
        inc (T);
        GetNeighbour (W,H,T,Wn,Hn);

        If (Hn > 0) and (Hn <= Height) then
            landlocked := not (GetTerrain (Wn, Hn) = Coast);
    end;
    result := not LandLocked;
end;


function tMap.StartScore (W,H : Integer; out DeadLand : Boolean) : Integer;
var Wt,Ht,T,F,P,Ft,Mt,Tr,Trt : Integer;
    Coastal,
    DL : Boolean;
    Tile : tTileData;
    TileArray : tTileArray;

begin
    TileArray := default (tTileArray);
    F := 0;
    P := 0;
    Tr:= 0;
    DeadLand := False;
    Coastal := CoastalCity (W,H);

    // H varies between 1 and Height
    // Avoid tiles close to the edges
    if (H > 3) and (H < Height -2) then
        for T := 0 to 20 do
        begin
            GetCityNeighbour (W, H, T, Wt, Ht);
            TileResource (Wt, Ht, Ft, Mt, Trt, DL);

            with Tile Do
            begin
                Food  := Ft;
                Prod  := Mt;
                Trade := Trt;
                Tt    := GetTerrain (Wt, Ht);
            end;

            TileArray[T] := Tile;
            DeadLand := DeadLand or DL;
        end;

    TileSort (TileArray);

    // Allways include the city square
    with TileArray[0] do
    begin
        F  := Food;
        P  := Prod;
        Tr := Trade;
    end;

    T := 1;
    while (T <= 20) and (TileArray[T].Food + F > (T+1) * 2) do
    begin
        // Enough food for another tile
        with TileArray[T] do
        begin
            F  := F + Food;
            P  := P + Prod;
            Tr := Tr + Trade;
        end;
        T := T+1;
    end;

    { Bonus for coastal city }
    result := trunc (2.5*Integer (Coastal) + sqrt (sqrt ((F*P*P*Tr)))); // reduce number to a convenient level
end;

Procedure tMap.AddCity (W,H,S : Integer; T : tTerrain; DL : Boolean);
var ptr : tTileptr;
begin
    New (ptr);
    with ptr^ do
    begin
        Wid   := W;
        Hgt   := H;
        Score := S;
        Tile  := T;
        Cull  := False;
        DeadLand := DL;
    end;
    Citylist.add (ptr);
end;

function tMap.Dist (W1,H1,W2,H2 : Integer) : Integer;
var Wd, Hd : Integer;
begin
    Hd := Abs (H1-H2) div 2;
    Wd := Abs (W1-W2);

    { Round World, Horizontal distance cannot be more than half the width }
    if Wd > Width Div 2 then
        Wd := Width - Wd;

    result := Round (sqrt (Hd*Hd+Wd*Wd));
end;

function tMap.GetCityIndex (C : integer) : integer;
begin
    with tTileptr (Citylist.Items[C])^ do
        result := MapIndex (Wid, Hgt) -1;
end;

procedure tMap.CullCitySites;
var C1, C2 : Longint;
   Cp1, Cp2 : tTileptr;

begin
    For C1 := 0 to Citylist.Count-1 do
    begin
        Cp1 := Citylist.Items[C1];
        C2  := Citylist.Count-1;

        if not Cp1^.Cull then
        While (C2 > C1) do
        begin
            Cp2 := Citylist.Items[C2];

            // Cull all Cp2 cities too close to this Cp1
            if Dist (Cp1^.Wid, Cp1^.Hgt, Cp2^.Wid, Cp2^.Hgt) < MinCityDist then
                Cp2^.Cull := True;

            Dec (C2);
         end;
    end;

    For C1 := 0 to Citylist.Count-1 do
    begin
        Cp1 := Citylist.Items[C1];
        If Cp1^.cull then
        begin
            Dispose (Cp1);
            Citylist.Items[C1] := nil;
        end
    end;

    CityList.Pack;
end;

function tMap.StartPosition (W, H : Integer) : Boolean;
begin
    result := Tiles [W,H].StartPos;
end;

procedure tMap.SetStartPosition (W, H : Integer);
begin
    Tiles [W,H].StartPos := True;
end;



procedure tMap.GetCitySites;
var W, H, S : Integer;
    DL : Boolean;
    T : tTerrain;

begin
    ClearCityList;
    for W := 1 to Width do
        for H := 4 to Height -3 do
        begin
            {Check each Tile for possible city site}
            T := GetTerrain (W,H);
            
            if (T in [Grass, Prairie, Hills, Tundra])
            or ( (T = Desert) and (GetBonus (W,H) = EarlyBonus)) //Oasis
            or ( (T = Arctic) and (GetBonus (W,H) = EarlyBonus) and Tiles [W,H].River) then // Seals with River
            begin
                S := StartScore (W,H,DL);
                If S >= MinCityScore then
                    AddCity (W,H,S,T,DL);
            end;
        end;

    { Sort City Sites by score value }
    Citylist.sort (@CitySort);

    { Cull overlapping entries }
    CullCitySites;
end;


procedure tMap.PlaceStartPositions;
var C : Integer;
    Cs : tTileptr;
begin
    GetCitySites;
    AllocateDeadLands;

    { Allocate Starting Positions }
    for C := 1 to min (NumStartPos, Citylist.Count) do
    begin
        Cs := Citylist.Items[C-1];
        with Cs^ do
            SetStartPosition (Wid, Hgt);
    end;
end;
