unit _HexMap;

interface

Uses
  QuadEngine,
  _Geometry,
  _Resources,
  _Forms,
  _SysUtils,  
  _Scene,
  _Units,
  _Effects,
  PngImage;

const            
  MAP_WIDTH  = 22;
  MAP_HEIGHT = 12;
  TILE_SIZE  = 64;

  COMPUTER_WAIT  = 0.6;

  MAX_SIDES  = 2;

  MAX_WAYPOINTS = 20;  
  MOVE_ANIMATION_SPEED = 0.14;   
  MapDrawShift: TVector2 = (X: (13 - 64); Y: -46);

type
TTileType = (
  TNone  = 0,
  TPlane = 1, //All can move
  THills = 2, //Only infantry  - high protection
  TForest= 3, //Only infantry  - high protection
  TWater = 4  //ships only
  );

TUnitType = (
  UVehicle  = 0,//planes
  UInfantry = 1,//Planes and hills
  UShip     = 2,//Water
  UAirCraft = 3 //all except none
  );

TGameMode = (
  GMPlayerVsComputer   = 0, 
  GMPlayerVsPlayer     = 1,
  GMComputerVsComputer = 2
  );

TBaseUnit = class;

TWayPoint = record
  Point: array [0..MAX_WAYPOINTS] of TVector2;
  Size: Byte;
end;

TTile = record
  Tile: Byte;
  Tip: TTileType;
  Step: Integer;        
  Fire: Integer;
  Marker: Boolean;
  UnitOnTile: TBaseUnit;
  Potential: Integer;
  FirePotential: Integer;
  DamagePotential: Integer;
end;

TAI = class;

THexMap = class
  private
    const PATH = 'Data/Levels/';
  private
    Map: array [0 .. MAP_WIDTH,0 .. MAP_HEIGHT] of TTile;
    FSides: array [0..MAX_SIDES] of TAi;
    FCurrentSide,
    FMaxSides: Byte;  
    FEffects: TEffects;
    procedure DrawTiles;            
    procedure DrawUnits;
    procedure MarkMovePoints(StartPoint: TPoint; MovementPoints: Integer; UnitTip: TUnitType);
    function MarkFireRadius(SelectPoint: TPoint; FireRadius: Byte; Side: Byte): Boolean;   
    procedure FireToUnit(PointUnit, SelectedUnit: TBaseUnit);
    function  FindWayPoints(StartPoint, FinishPoint: TPoint): TWayPoint;
    procedure ClearMarks(Value: Boolean = true);
  public
    constructor Create();
    function  ComputerTurn(): Boolean;
    procedure LoadMap(pngImage: String; GameMode: TGameMode = GMPlayerVsComputer);
    function  GetMap(Point: TPoint): TTile;
    function  GetTileCoords(MouseCoords: TPoint): TPoint;
    function  SelectTile(SelectPoint: TPoint): TBaseUnit;      
    function  ShowInfo(SelectPoint: TPoint): TBaseUnit;
    function  CommandToTile(SelectedUnit: TBaseUnit; SelectPoint: TPoint): TBaseUnit;
    procedure EndTurn();
    procedure Render;
    procedure Update(dt: single);
    property Side: Byte read FCurrentSide;
  end;

TBaseUnit = class
  protected
    FMap: THexMap;
    FPosition: TPoint;
    FType: TUnitType;
    Tile: Byte;
    Side: Byte;
    MovementPoint: Byte;
    FAttackRadius: Byte;
    FFirePower: Byte;
    Moved,            //Move Phase ended
    Fired: Boolean;   //Fire Phase ended
    FWayPoint: TWayPoint; 
    FMoveAnimation: Single;
    FLife: Integer;
    FLifePosition: Single;
    FRadiusAnimation: Byte;
    procedure DrawLifePoints(Point: TVector2);
    procedure Die();
  public
    constructor Create(Map: THexMap; Point: TPoint; Side: Byte);
    function  Fire(Tile: TTileType): Integer;
    procedure Draw(Color: Cardinal = $ffffffff); virtual;
    procedure Update(dt: single); virtual;
    procedure SetWayPoint(WayPoint: TWayPoint);
    procedure ShowFireRadius();
    property  Position: TPoint read FPosition write FPosition;
  end;

TBazookaUnit = class(TBaseUnit)
  public
    constructor Create(Map: THexMap; Point: TPoint; Side: Byte);
end;

TGunnerUnit = class(TBaseUnit)
  public
    constructor Create(Map: THexMap; Point: TPoint; Side: Byte);
end;

TCarUnit = class(TBaseUnit)
  public
    constructor Create(Map: THexMap; Point: TPoint; Side: Byte);
end;

TTankUnit = class(TBaseUnit)
  public
    constructor Create(Map: THexMap; Point: TPoint; Side: Byte);
end;

TArtilleryUnit = class(TBaseUnit)
  public
    constructor Create(Map: THexMap; Point: TPoint; Side: Byte);
end;

TAIType = (
  ControlByComputer,
  ControlByPlayer
  );

TAI = class
  private
    FWaiting: Single;
    FAIType:  TAIType;
    FSide:    Byte;  
    FMap:     THexMap;
    FBasicDataCollected: Boolean; 
    FUnits: array of TBaseUnit;//Units under ai control  
    FUnitCount: Word;//Units under ai control
    procedure CollectDataAboutUnits;             
    procedure FindMovePotential(SelectedUnit: TBaseUnit);  
    procedure FindFirePotential(SelectedUnit: TBaseUnit);
    procedure FindDamagePotential(SelectedUnit: TBaseUnit);
  public
    constructor Create(Map: THexMap; Side: Byte; AIType: TAIType = ControlByComputer);
    procedure MakeTurn(SelectedUnit: TBaseUnit);
    procedure Use(dt: Single);
end;

function SideColor(Side: Byte; TurnEnded: Boolean = false): Cardinal;

implementation

Uses _World;

{$REGION 'Convert tile coords to real'}
function ConvertToHex(Point: TPoint): TVector2; overload;
begin
  Result:= Vector2(Point.X * TILE_SIZE - Point.Y * TILE_SIZE * 0.5, Point.Y * TILE_SIZE) + MapDrawShift;
end;

function ConvertToHex(X, Y: Single): TVector2; overload;
begin
  Result:= Vector2(X * TILE_SIZE - Y * TILE_SIZE * 0.5, Y * TILE_SIZE) + MapDrawShift;
end;
{$ENDREGION}

{$REGION 'HexMap'}
constructor THexMap.Create;
var
  i, j: Word;
  WayPoint: TWayPoint;
begin
//params
  FCurrentSide:= 0;
  FMaxSides:= 1;
  FEffects:= TEffects.Create();
  for i:=0 to FMaxSides do
    FSides[i]:= TAI.Create(Self, i);
  ClearMarks();
end;

function  THexMap.GetMap(Point: TPoint): TTile;
begin
  if  (Point.X < 0) or
      (Point.Y < 0) or
      (Point.X > MAP_WIDTH) or
      (Point.Y > MAP_WIDTH)
  then
    Exit
  else
    Result:= Map[Point.X, Point.Y];
end;

function THexMap.GetTileCoords(MouseCoords: TPoint): TPoint;
var
  x, y: integer;
begin
  y:= trunc(MouseCoords.Y - MapDrawShift.Y) div TILE_SIZE;
  x:= trunc(MouseCoords.X - MapDrawShift.X + y * TILE_SIZE * 0.5) div TILE_SIZE;
  Result:= Point(x, y);
end;

procedure THexMap.ClearMarks(Value: Boolean = true);
var
  i, j: Word;
begin
  for i:= 0 to MAP_WIDTH do
    for j:= 0 to MAP_HEIGHT do
      begin
        Map[i, j].Marker:= Value;
        Map[i, j].Step:= 0;
      end;
end;
      
{$REGION 'Moving'}
procedure THexMap.MarkMovePoints(StartPoint: TPoint; MovementPoints: Integer; UnitTip: TUnitType);
  procedure ChowNearest(Point: TPoint; MovementPoints: Integer; UnitTip: TUnitType);
  begin
    if MovementPoints < 0 then
      exit;
    if  (Point.X < 0) or
        (Point.Y < 0) or
        (Point.X > MAP_WIDTH) or
        (Point.Y > MAP_HEIGHT) then
      Exit;
    if Map[Point.X, Point.Y].Tip = TNone then
      exit;
    if MovementPoints < Map[Point.X, Point.Y].Step then
      exit;
    if (Point <> StartPoint) then
      if Map[Point.X, Point.Y].UnitOnTile <> nil then
        exit;

    case UnitTip of
      UInfantry:
      begin
        if not ((Map[Point.X, Point.Y].Tip = TPlane) or
                (Map[Point.X, Point.Y].Tip = THills) or
                (Map[Point.X, Point.Y].Tip = TForest)) then
          Exit;
      end;
      UVehicle:if (Map[Point.X, Point.Y].Tip <> TPlane) then exit;
      UShip:;
      UAirCraft:;
    end;
    Map[Point.X, Point.Y].Marker:= true;
    Map[Point.X, Point.Y].Step:= MovementPoints;
    ChowNearest(Point + TPoint.Point( 0, 1) , MovementPoints - 1 ,UnitTip);
    ChowNearest(Point + TPoint.Point( 0,-1) , MovementPoints - 1 ,UnitTip);
    ChowNearest(Point + TPoint.Point(-1, 0) , MovementPoints - 1 ,UnitTip);
    ChowNearest(Point + TPoint.Point( 1, 0) , MovementPoints - 1 ,UnitTip);
    ChowNearest(Point + TPoint.Point( 1, 1) , MovementPoints - 1 ,UnitTip);
    ChowNearest(Point + TPoint.Point(-1,-1) , MovementPoints - 1 ,UnitTip);
  end;
begin
  ChowNearest(StartPoint, MovementPoints, UnitTip);
end;

function  THexMap.FindWayPoints(StartPoint, FinishPoint: TPoint): TWayPoint;
  function IsNearest(Point, Nearest: TPoint): Boolean;
  begin
    Nearest:=  Point + Nearest;
    Result:= (Map[Point.X, Point.Y].Step < Map[Nearest.X, Nearest.Y].Step) and (Map[Nearest.X, Nearest.Y].Marker)
  end;
  function FindNearestWayPoint(Point: TPoint): TPoint;
  begin
    if IsNearest(Point,  TPoint.Point( 0, 1)) then
      Result:= Point + TPoint.Point( 0, 1)
    else
      if IsNearest(Point,  TPoint.Point( 0,-1)) then
        Result:= Point + TPoint.Point( 0,-1)
      else
        if IsNearest(Point,  TPoint.Point( 1, 0)) then
          Result:= Point + TPoint.Point( 1, 0)
        else
          if IsNearest(Point,  TPoint.Point(-1, 0)) then
            Result:= Point + TPoint.Point(-1, 0)
          else
            if IsNearest(Point,  TPoint.Point( 1, 1)) then
              Result:= Point + TPoint.Point( 1, 1)
            else
              Result:= Point + TPoint.Point(-1,-1);
  end;
var
  WayPoints: TWayPoint;
  CurrentPoint: TPoint;
  Count: Integer;
begin
  CurrentPoint:=  FinishPoint;
  Count:= 0;
  WayPoints.Point[Count]:= ConvertToHex(CurrentPoint);
  Inc(Count);
  Repeat
    CurrentPoint:= FindNearestWayPoint(CurrentPoint);
    WayPoints.Point[Count]:= ConvertToHex(CurrentPoint);
    Inc(Count);
  Until ((CurrentPoint = StartPoint) or (Count > MAX_WAYPOINTS));
  WayPoints.Size:= Count - 1;
  Result:= WayPoints;
end;

function  THexMap.ShowInfo(SelectPoint: TPoint): TBaseUnit;
begin
  if (Map[SelectPoint.X, SelectPoint.Y].UnitOnTile <> nil) then
    begin
      Map[SelectPoint.X, SelectPoint.Y].UnitOnTile.ShowFireRadius;
      Result:= Map[SelectPoint.X, SelectPoint.Y].UnitOnTile;
    end
  else
    Result:= nil;
end;
{$ENDREGION}

{$REGION 'Select Tile'}
function  THexMap.SelectTile(SelectPoint: TPoint): TBaseUnit;
var
  SelectedUnit: TBaseUnit;
begin
  Result:= nil;
  if (Map[SelectPoint.X, SelectPoint.Y].UnitOnTile <> nil) then
    begin
      if (GetMap(SelectPoint).UnitOnTile.Side = FCurrentSide) then
        begin
          ClearMarks(false);
          SelectedUnit:= GetMap(SelectPoint).UnitOnTile;
          Result:= SelectedUnit;
          if (not SelectedUnit.Moved) then
            begin
              MarkMovePoints(SelectPoint, SelectedUnit.MovementPoint, SelectedUnit.FType);
              MarkFireRadius(SelectPoint, SelectedUnit.FAttackRadius, SelectedUnit.Side);
            end
          else
            if (not SelectedUnit.Fired) then
              begin
                if not MarkFireRadius(SelectPoint, SelectedUnit.FAttackRadius, SelectedUnit.Side) then
                  begin
                    ClearMarks();
                    SelectedUnit.Fired:= true;
                  end;
              end
            else
              ClearMarks(); 
        end;
    end
  else
    begin
      ClearMarks();
    end;
end;

function   THexMap.CommandToTile(SelectedUnit: TBaseUnit; SelectPoint: TPoint): TBaseUnit;
var
  WayPoint: TWayPoint; 
  StartPoint: TPoint;
  CommandUnit: TBaseUnit;
begin
  Result:= nil;
  if not Map[SelectPoint.X, SelectPoint.Y].Marker then
    begin
      ClearMarks();
      Exit;
    end;
  if Map[SelectPoint.X, SelectPoint.Y].UnitOnTile = nil then
    begin // move
      if (not SelectedUnit.Moved) then
      begin
        StartPoint:= SelectedUnit.FPosition;
        WayPoint:= FindWayPoints(StartPoint, SelectPoint);
        Map[SelectPoint.X, SelectPoint.Y].UnitOnTile:= Map[StartPoint.X, StartPoint.Y].UnitOnTile;
        SelectedUnit.Position:= Point(SelectPoint.X, SelectPoint.Y);
        Map[StartPoint.X, StartPoint.Y].UnitOnTile:= nil;
        SelectedUnit.FPosition:= SelectPoint;
        SelectedUnit.SetWayPoint(WayPoint);
        SelectedUnit.Moved:= true;
        ClearMarks(false);
        SelectTile(SelectPoint);    
        Result:= SelectedUnit;
      end;
    end
  else
    begin //battle  
      if (not SelectedUnit.Fired) then
      begin
        CommandUnit:= Map[SelectPoint.X, SelectPoint.Y].UnitOnTile;
        if CommandUnit.Side <> SelectedUnit.Side then
          FireToUnit(CommandUnit, SelectedUnit);
        SelectedUnit.Moved:= true;
        SelectedUnit.Fired:= true; 
        ClearMarks(true);
      end;
    end;
end;
{$ENDREGION}

{$REGION 'Fire'}                                                 
function THexMap.MarkFireRadius(SelectPoint: TPoint; FireRadius: Byte; Side: Byte): Boolean;
var
  i, j: Byte;
  UnitPosition: TVector2;
  MarkFlag: Boolean;
  procedure ChowFireZone(x, y: Byte; Distance: Byte);
  begin
    if (Map[x, y].Tip = TNone) or (Map[x, y].Tip = TWater) then
      Exit;
    if Map[x, y].Fire > Distance then
      Exit;
    if Distance = 0 then
      Exit;
    Map[x, y].Fire:= Distance;
    ChowFireZone(x + 1, y, Distance - 1);
    ChowFireZone(x, y + 1, Distance - 1);
    ChowFireZone(x - 1, y, Distance - 1);
    ChowFireZone(x, y - 1, Distance - 1);
    ChowFireZone(x + 1, y + 1, Distance - 1);
    ChowFireZone(x - 1, y - 1, Distance - 1);
  end;
begin
  MarkFlag:= false;
  for i:= 0 to MAP_WIDTH do   
    for j:= 0 to MAP_HEIGHT do
      Map[i, j].Fire:= 0;
  ChowFireZone(SelectPoint.X, SelectPoint.Y, FireRadius + 1);
  
  for i:= 0 to MAP_WIDTH do   
    for j:= 0 to MAP_HEIGHT do
      if Map[i, j].Fire > 0 then
        if Map[i, j].UnitOnTile <> nil then
          if Map[i, j].UnitOnTile.Side <> Side then  
            begin
              Map[i, j].Marker:= true;
              MarkFlag:= true;
            end;
  Result:= MarkFlag;
end;
           
procedure THexMap.FireToUnit(PointUnit: TBaseUnit; SelectedUnit: TBaseUnit);
var
  Damage: String;
begin
  Damage:= '-' + IntToStr(SelectedUnit.Fire(GetMap(PointUnit.FPosition).Tip));
  FEffects.AddTextEffect(
    Damage,
    ConvertToHex(PointUnit.Position),
    $44ff44,
    150);
  PointUnit.FLife:= PointUnit.FLife - SelectedUnit.Fire(GetMap(PointUnit.FPosition).Tip);
  if (PointUnit.FLife <= 0) then
    PointUnit.Die()
  else
    if ((PointUnit.FAttackRadius + 0.5) * TILE_SIZE > (ConvertToHex(PointUnit.Position) - ConvertToHex(SelectedUnit.Position)).Length) then
      begin                         
        Damage:= '-' + IntToStr(SelectedUnit.Fire(GetMap(SelectedUnit.FPosition).Tip));
        FEffects.AddTextEffect(
          Damage,
          ConvertToHex(SelectedUnit.Position),
          $ff4444,
          150);
        SelectedUnit.FLife:= SelectedUnit.FLife - PointUnit.Fire(GetMap(SelectedUnit.FPosition).Tip);
        if (SelectedUnit.FLife <= 0) then
          SelectedUnit.Die();
      end;
end;
{$ENDREGION}

        
function THexMap.ComputerTurn(): Boolean;
begin
  Result:=  FSides[FCurrentSide].FAIType = ControlByComputer;
end;

procedure THexMap.EndTurn();
var
  i, j: Word;
begin  
  ClearMarks();
  Inc(FCurrentSide);
  if (FMaxSides < FCurrentSide) then
    FCurrentSide:= 0;
  for i:= 0 to MAP_WIDTH do
    for j:= 0 to MAP_HEIGHT do
      if (Map[i,j].UnitOnTile <> nil) then      
        if (Map[i,j].UnitOnTile.Side = FCurrentSide) then
          begin
            Map[i,j].UnitOnTile.Moved:= false;
            Map[i,j].UnitOnTile.Fired:= false;
          end;
end;

{$REGION 'Render'}
procedure THexMap.DrawTiles();
var
  i, j: Word;
  Color: Cardinal;
  Coord: TVector2;
begin
  for i:= 0 to MAP_WIDTH do
    for j:= 0 to MAP_HEIGHT do
        begin
          Color:= $ffffffff;
          if Not Map[i, j].Marker then
            Color:= $ffcccccc;
          if (Map[i, j].Marker) and (Map[i, j].UnitOnTile <> nil) then
            Color:= $ffeeeeee;    
          Coord:= ConvertToHex(Point(i, j));
          Resources.TileTexture.Draw(Coord.X, Coord.Y, Color, Map[i,j].Tile);
{          //TODO Debug info
          Resources.Font.TextOut(Coord.X, Coord.Y, 0.3, PAnsiChar(IntToStr(Map[i, j].Potential)));
          //TODO Debug info
          Resources.Font.TextOutAligned(Coord.X + TILE_SIZE, Coord.Y, 0.3, PAnsiChar(IntToStr(Map[i, j].FirePotential)), $ff0000ff, qfaRight); 
          //TODO Debug info
          Resources.Font.TextOutAligned(Coord.X + TILE_SIZE, Coord.Y + TILE_SIZE - 15, 0.3, PAnsiChar(IntToStr(Map[i, j].DamagePotential)), $ffff0000, qfaRight);
}        end;
end;

procedure THexMap.DrawUnits;
var
  i, j: Word;
  Color: Cardinal;
begin
  for i:= 0 to MAP_WIDTH do
    for j:= 0 to MAP_HEIGHT do
      if (Map[i,j].UnitOnTile <> nil) then
        begin
          Color:= $ffffffff;
          if Not Map[i,j].Marker then
            Color:= $ffcccccc;
          Map[i,j].UnitOnTile.Draw(Color);
        end;
end;

procedure THexMap.Render;
begin
  DrawTiles();
  DrawUnits();
end;    
{$ENDREGION}

procedure THexMap.Update(dt: Single);  
var
  i, j: Word;
begin
  for i:= 0 to MAP_WIDTH do
    for j:= 0 to MAP_HEIGHT do
      if (Map[i, j].UnitOnTile <> nil) then
        begin
          Map[i, j].UnitOnTile.Update(dt);
        end;
  FSides[FCurrentSide].Use(dt);
end;
{$ENDREGION}

{$REGION 'Load Map'}
procedure THexMap.LoadMap(pngImage: String; GameMode: TGameMode = GMPlayerVsComputer);
var
  i, j: Word;
  mapImage: TpngObject;
begin
  mapImage:= TpngObject.Create;
  mapImage.LoadFromFile(PATH + pngImage + '.png');
  for i:= 0 to MAP_WIDTH do
    for j:= 0 to MAP_HEIGHT do
      begin
        if Map[i, j].UnitOnTile <> nil then 
          Map[i, j].UnitOnTile.free;
        Map[i, j].UnitOnTile:= nil;
      end;
  for i:= 0 to MAP_WIDTH do
    for j:= 0 to MAP_HEIGHT do
    case mapImage.Pixels[i,j] of
      $cccccc:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
        end;
      $ffffff://Planev
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
        end;
      $000000: //Rails
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= 8 + Random(2);
        end;
      $ff0000://Water
        begin
          Map[i, j].Tip:= TWater;
          Map[i, j].Tile:= 10 + Random(2) + Random(2) * 4;
        end;
      $00ff00://forest
        begin
          Map[i, j].Tip:= TForest;
          Map[i, j].Tile:= 2 + Random(2) + Random(2) * 4;
        end;
      //Self Units
      $0000ff: 
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TBaseUnit.Create(Self, Point(i, j), 0);
        end;
      $0000dd:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TBazookaUnit.Create(Self, Point(i, j), 0);
        end;
      $0000bb:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TGunnerUnit.Create(Self, Point(i, j), 0);
        end;
      $000099:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TCarUnit.Create(Self, Point(i, j), 0);
        end;
      $000077:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TTankUnit.Create(Self, Point(i, j), 0);
        end;
      $000055:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TArtilleryUnit.Create(Self, Point(i, j), 0);
        end;
      //enemy Units
      
      $ff00ff:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TBaseUnit.Create(Self, Point(i, j), 1);
        end;
      $dd00dd:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TBazookaUnit.Create(Self, Point(i, j), 1);
        end;
      $bb00bb:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TGunnerUnit.Create(Self, Point(i, j), 1);
        end;
      $990099:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TCarUnit.Create(Self, Point(i, j), 1);
        end;
      $770077:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TTankUnit.Create(Self, Point(i, j), 1);
        end;
      $550055:
        begin
          Map[i, j].Tip:= TPlane;
          Map[i, j].Tile:= Random(2) + Random(2) * 4;
          Map[i, j].UnitOnTile:= TArtilleryUnit.Create(Self, Point(i, j), 1);
        end;
      else ;//SetTile(i,j,0,CELL_FREE);
    end;
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      begin
        if  (ConvertToHex(i, j).X < 0) or (ConvertToHex(i, j).X > 1024 - TILE_SIZE) or
            (ConvertToHex(i, j).Y < 0) or (ConvertToHex(i, j).Y > 768 - TILE_SIZE) then
          Map[i, j].Tip:= TNone;
      end;
  case GameMode of
    GMPlayerVsComputer:
      begin
        FSides[0]:= TAI.Create(Self, 0, ControlByPlayer);
        FSides[1]:= TAI.Create(Self, 1, ControlByComputer);
      end;
    GMPlayerVsPlayer:
      begin
        FSides[0]:= TAI.Create(Self, 0, ControlByPlayer);
        FSides[1]:= TAI.Create(Self, 1, ControlByPlayer);
      end;
    GMComputerVsComputer:
      begin       
        FSides[0]:= TAI.Create(Self, 0, ControlByComputer);
        FSides[1]:= TAI.Create(Self, 1, ControlByComputer);
      end;
  end;
end;
{$ENDREGION}

{$REGION 'SideColor'}
function SideColor(Side: Byte; TurnEnded: Boolean = false): Cardinal;
begin
  if not TurnEnded then
    case Side of
      0:Result:= $ff0000ff;
      1:Result:= $ff00ff00;
      2:Result:= $ffff0000;
      3:Result:= $ff999999;
      else Result:= $ffffffff;
    end
  else
    case Side of
      0:Result:= $ff000099;
      1:Result:= $ff009900;
      2:Result:= $ff990000;
      3:Result:= $ff666666;
      else Result:= $ff999999;
    end;
end;
{$ENDREGION}

{$REGION 'BaseUnit'}
constructor TBaseUnit.Create(Map: THexMap; Point: TPoint; Side: Byte);
var
  WayPoint: TWayPoint;
begin
  FMap:= Map;
  Tile:= 0;   
  Moved:= false;
  Fired:= false;   
  FMoveAnimation:= 0;
  MovementPoint:= 3;
  FAttackRadius:= 1;
  FFirePower:= 2;

  FType:= UInfantry;
  Self.Side:= Side;
  FLife:= 3;
  FLifePosition:= FLife / 2;
  FPosition:= Point; 
  WayPoint.Point[0]:= ConvertToHex(Point.X, Point.Y);
  WayPoint.Point[1]:= ConvertToHex(Point.X, Point.Y);
  WayPoint.Size:= 0;
  Self.SetWayPoint(WayPoint);
end;

function  TBaseUnit.Fire(Tile: TTileType): Integer;
begin
  Result:= FFirePower;
  if (Tile = TForest) or (Tile = THills) then
    Result:= 1;
end;

procedure TBaseUnit.Die();
begin
  FMap.Map[FPosition.X, FPosition.Y].UnitOnTile:= nil;
  Self.Free;
end;

procedure TBaseUnit.SetWayPoint(WayPoint: TWayPoint);
begin
  FWayPoint:= WayPoint;
  FMoveAnimation:= WayPoint.Size * MOVE_ANIMATION_SPEED;
end;

procedure TBaseUnit.ShowFireRadius();
begin
  FRadiusAnimation:= 252;
end;

procedure TBaseUnit.DrawLifePoints(Point: TVector2);
var
  i: Byte;
begin
  Resources.Quad.Rectangle(
    Point.X + TILE_SIZE * 0.5 + (-FLifePosition + FLife - 0.5) * TILE_SIZE / 8 + 4,
    Point.Y + TILE_SIZE - 0,
    Point.X + TILE_SIZE * 0.5 + (-FLifePosition + 1 - 0.5) * TILE_SIZE / 8 - 4,
    Point.Y + TILE_SIZE - 9,
    $77000000);
  for i:= 1 to FLife do
  begin
    Resources.Quad.Rectangle(
      Point.X + TILE_SIZE * 0.5 + (-FLifePosition + i - 0.5) * TILE_SIZE / 8 + 2.5,
      Point.Y + TILE_SIZE - 2,
      Point.X + TILE_SIZE * 0.5 + (-FLifePosition + i - 0.5) * TILE_SIZE / 8 - 2.5,
      Point.Y + TILE_SIZE - 7,
      SideColor(Side, Moved));
  end;
end;

procedure TBaseUnit.Draw(Color: Cardinal = $ffffffff);
var
  CurrentNode: Integer;
  Move: Single;
  Point: TVector2;
begin
  CurrentNode:= trunc((FMoveAnimation) / MOVE_ANIMATION_SPEED);
  if CurrentNode < 0 then
    CurrentNode:= 0;
  Move:=  1 - (FMoveAnimation / MOVE_ANIMATION_SPEED - CurrentNode);
  if Move < 0 then
    Move:= 0;
  Point:= FWayPoint.Point[CurrentNode].Lerp(FWayPoint.Point[CurrentNode + 1], Move);
  Resources.UnitTexture.Draw(Point.X, Point.Y, Color, Tile + Side * 4);
  DrawLifePoints(Point);
  Point:= Point + Vector2(TILE_SIZE * 0.5, TILE_SIZE * 0.5);
  Resources.RadiusTexture.DrawRot(
    Point.X,
    Point.Y, 0,
    1 / Resources.RadiusTexture.GetTextureWidth * (FAttackRadius + 0.5) * TILE_SIZE * 2.0,
    (FRadiusAnimation) * $1000000 + $ffffff);
end;

procedure TBaseUnit.Update(dt: single);
begin
  FMoveAnimation:= FMoveAnimation - dt;
  if FMoveAnimation < 0 then
    FMoveAnimation:= 0;       
  if FRadiusAnimation > 0 then
    FRadiusAnimation:= FRadiusAnimation - 4;   
end;
{$ENDREGION}

{$REGION 'TCarUnit'}
constructor TBazookaUnit.Create(Map: THexMap; Point: TPoint; Side: Byte);
begin
  inherited;
  Tile:= 1;
  FAttackRadius:= 2;
  FFirePower:= 3;
  FLife:= 3;
  FLifePosition:= FLife / 2;
end;
{$ENDREGION}

{$REGION 'TGunnerUnit'}
constructor TGunnerUnit.Create(Map: THexMap; Point: TPoint; Side: Byte);
begin
  inherited;
  Tile:= 2;
  FAttackRadius:= 1;
  FFirePower:= 2;
  FLife:= 4;
  FLifePosition:= FLife / 2;
end;
{$ENDREGION}

{$REGION 'TCarUnit'}
constructor TCarUnit.Create(Map: THexMap; Point: TPoint; Side: Byte);
begin
  inherited;
  Tile:= 8;
  MovementPoint:= 4;
  FType:= UVehicle;
  FAttackRadius:= 1;
  FFirePower:= 3;
  FLife:= 6;
  FLifePosition:= FLife / 2;
end;
{$ENDREGION}

{$REGION 'TankUnit'}
constructor TTankUnit.Create(Map: THexMap; Point: TPoint; Side: Byte);
begin
  inherited;
  Tile:= 9;
  MovementPoint:= 4;
  FType:= UVehicle;
  FAttackRadius:= 2;
  FFirePower:= 3;
  FLife:= 8;
  FLifePosition:= FLife / 2;
end;
{$ENDREGION}

{$REGION 'TArtilleryUnit'}
constructor TArtilleryUnit.Create(Map: THexMap; Point: TPoint; Side: Byte);
begin
  inherited;
  Tile:= 10;
  MovementPoint:= 2;     
  FFirePower:= 5;
  FAttackRadius:= 4;
  FType:= UVehicle;
  FLife:= 4; 
  FLifePosition:= FLife / 2;
end;
{$ENDREGION}

{$REGION 'AI'}
constructor TAI.Create(Map: THexMap; Side: Byte; AIType: TAIType = ControlByComputer);
begin
  FAIType:= AIType;
  FMap:= Map;
  FSide:= Side;
  FWaiting:= 0;
end;

procedure TAI.Use(dt: Single);
var
  i,
  ActiveCount: Byte;
begin
  if FAIType = ControlByPlayer then
    Exit;
  if not FBasicDataCollected then
    CollectDataAboutUnits;   
  FBasicDataCollected:= false;
    
  FWaiting:= FWaiting - dt;
  if FWaiting <= 0 then
    begin         
      if FUnitCount = 0 then
        begin
          if FSide = 0 then
            World.Lose
          else
            World.Win;
      end;
      ActiveCount:= 0;
      for i:= 0 to FUnitCount - 1 do
        if not FUnits[i].Moved then
          Inc(ActiveCount);
      if (ActiveCount = 0) then
        begin
          FWaiting:= COMPUTER_WAIT;
          FBasicDataCollected:= false;
          FMap.EndTurn;
        end
      else
        for i:= 0 to FUnitCount - 1 do
          if not FUnits[i].Fired then
            begin
              MakeTurn(FUnits[i]);
              FWaiting:= COMPUTER_WAIT;
              break;
            end;
    end;
end;

procedure TAI.CollectDataAboutUnits;
var
  i, j: Byte;
begin
  FUnitCount:= 0;
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      if FMap.Map[i, j].UnitOnTile <> nil then
        if FMap.Map[i, j].UnitOnTile.Side = Self.FSide then
          begin
            Inc(FUnitCount);
          end;
  SetLength(FUnits, FUnitCount);
  FUnitCount:= 0;
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      if FMap.Map[i, j].UnitOnTile <> nil then
        if FMap.Map[i, j].UnitOnTile.Side = Self.FSide then
          begin
            FUnits[FUnitCount]:= FMap.Map[i, j].UnitOnTile;
            Inc(FUnitCount);
          end;
  FBasicDataCollected:= true;
end;           

procedure TAI.FindMovePotential(SelectedUnit: TBaseUnit);
var
  i, j: Byte;
  procedure MarkMovePotential(x, y: Byte; Points: Byte);
  begin
    if (Points <= FMap.Map[x, y].Potential) then
      Exit;
    if (FMap.Map[x, y].Tip = TNone) or (FMap.Map[x, y].Tip = TWater) then
      Exit;
    FMap.Map[x, y].Potential:= Points;
    MarkMovePotential(x + 1, y, Points - 1);
    MarkMovePotential(x, y + 1, Points - 1);
    MarkMovePotential(x - 1, y, Points - 1);
    MarkMovePotential(x, y - 1, Points - 1);
    MarkMovePotential(x + 1, y + 1, Points - 1);
    MarkMovePotential(x - 1, y - 1, Points - 1);
  
  end;
begin
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      FMap.Map[i, j].Potential:= 0;
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      if FMap.Map[i, j].UnitOnTile <> nil then
        if FMap.Map[i, j].UnitOnTile.Side <> Self.FSide then
          MarkMovePotential(i, j, MAP_WIDTH + MAP_HEIGHT);
end;

procedure TAI.FindFirePotential(SelectedUnit: TBaseUnit);
var
  i, j: Byte;
  procedure ChowFireZone(x, y: Byte; Distance: Byte);
  begin
    if (FMap.Map[x, y].Tip = TNone) or (FMap.Map[x, y].Tip = TWater) then
      Exit;
    if FMap.Map[x, y].Fire > Distance then
      Exit;
    if Distance = 0 then
      Exit;
    FMap.Map[x, y].FirePotential:= Distance;
    ChowFireZone(x + 1, y, Distance - 1);
    ChowFireZone(x, y + 1, Distance - 1);
    ChowFireZone(x - 1, y, Distance - 1);
    ChowFireZone(x, y - 1, Distance - 1);
    ChowFireZone(x + 1, y + 1, Distance - 1);
    ChowFireZone(x - 1, y - 1, Distance - 1);
  end;
begin
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      FMap.Map[i, j].FirePotential:= 0;
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      if FMap.Map[i, j].UnitOnTile <> nil then
        if FMap.Map[i, j].UnitOnTile.Side <> Self.FSide then
          ChowFireZone(i, j, SelectedUnit.FAttackRadius + 1);
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      if FMap.Map[i, j].FirePotential > 0 then
        FMap.Map[i, j].FirePotential:= 1;
end;

procedure TAI.FindDamagePotential(SelectedUnit: TBaseUnit);
var
  i, j: Byte;
  procedure ChowDamageZone(x, y: Byte; Distance: Byte);
  begin
    if (FMap.Map[x, y].Tip = TNone) or (FMap.Map[x, y].Tip = TWater) then
      Exit;
    if FMap.Map[x, y].Fire > Distance then
      Exit;
    if Distance = 0 then
      Exit;
    FMap.Map[x, y].DamagePotential:= Distance;
    ChowDamageZone(x + 1, y, Distance - 1);
    ChowDamageZone(x, y + 1, Distance - 1);
    ChowDamageZone(x - 1, y, Distance - 1);
    ChowDamageZone(x, y - 1, Distance - 1);
    ChowDamageZone(x + 1, y + 1, Distance - 1);
    ChowDamageZone(x - 1, y - 1, Distance - 1);
  end;
begin
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      FMap.Map[i, j].DamagePotential:= 0;
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      if FMap.Map[i, j].UnitOnTile <> nil then
        if FMap.Map[i, j].UnitOnTile.Side <> Self.FSide then
          ChowDamageZone(i, j, FMap.Map[i, j].UnitOnTile.FAttackRadius + 1);
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      if FMap.Map[i, j].DamagePotential > 0 then
        if (FMap.Map[i, j].Tip = TForest) or (FMap.Map[i, j].Tip = THills) then  
          FMap.Map[i, j].DamagePotential:= 1
        else
          FMap.Map[i, j].DamagePotential:= 2;
end;

procedure TAI.MakeTurn(SelectedUnit: TBaseUnit);
var
  i, j: Byte;
  Max: Integer;
  Selected: TPoint;
begin
  FMap.SelectTile(SelectedUnit.FPosition);
  FindMovePotential(SelectedUnit);
  FindFirePotential(SelectedUnit);    
  FindDamagePotential(SelectedUnit);
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      FMap.Map[i, j].Potential:= FMap.Map[i, j].Potential + FMap.Map[i, j].FirePotential * 60 - FMap.Map[i, j].DamagePotential * 20;
  Max:= 0;
  Selected:= Point(SelectedUnit.Position.X, SelectedUnit.Position.Y);
  for i:=0 to MAP_WIDTH do
    for j:=0 to MAP_HEIGHT do
      if FMap.Map[i, j].Marker then   
        if FMap.Map[i, j].Potential > Max then
          begin
            Selected:= Point(i, j);
            Max:= FMap.Map[i, j].Potential;
          end;
  FMap.CommandToTile(SelectedUnit, Selected);
end;
{$ENDREGION}
end.

