unit UGame;

{$mode delphi}{$H+}

interface

uses
  Classes, SysUtils, ExtCtrls, Graphics, XMLConf, XMLRead, XMLWrite,
  DOM, Math, LazFileUtils, UXMLUtils, Dialogs, Types, LCLType, LCLIntf, fgl,
  UGeometry;

const
  DefaultPlayerStartUnits = 5;
  HexCellMulX = 1.12;
  HexCellMulY = 1.292;
  SquareCellMulX = 1.05;
  SquareCellMulY = 1.05;
  TriangleCellMulX = 0.55;
  TriangleCellMulY = 1.05;
  MaxPlayerCount = 8;

type
  TGame = class;
  TPlayer = class;
  TView = class;
  TUnitMoves = class;
  TCells = class;
  TMap = class;
  TCellLinks = class;
  TMapArea = class;
  TClient = class;

  TTerrainType = (ttVoid, ttNormal, ttCity);

  { TCell }

  TCell = class
  private
    FArea: TMapArea;
    FMap: TMap;
    FPower: Integer;
    procedure SetArea(AValue: TMapArea);
    procedure SetPower(AValue: Integer);
  public
    Id: Integer;
    PosPx: TPoint;
    Polygon: TPolygon;
    Terrain: TTerrainType;
    PlayerId: Integer;
    Player: TPlayer;
    MovesFrom: TUnitMoves;
    MovesTo: TUnitMoves;
    NeighborsId: array of Integer;
    Neighbors: TCells;
    Mark: Boolean; // Temporary value
    Weight: Integer; // Temporary value
    Angle: Double; // Temporary value
    Links: TCellLinks;
    procedure ConnectTo(Cell: TCell);
    procedure DisconnectFrom(Cell: TCell);
    procedure Check;
    function NeighboringToVoid: Boolean;
    procedure AreaExtend;
    procedure FixRefId;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
    procedure Assign(Source: TCell);
    function IsVisible(View: TView): Boolean;
    function GetColor: TColor;
    function GetAvialPower: Integer;
    function GetAttackPower: Integer;
    function ToString: ansistring; override;
    constructor Create;
    destructor Destroy; override;
    property Power: Integer read FPower write SetPower;
    property Map: TMap read FMap write FMap;
    property Area: TMapArea read FArea write SetArea;
  end;

  TCellArray = array of TCell;

  { TCells }

  TCells = class(TFPGObjectList<TCell>)
    Map: TMap;
    procedure FixRefId;
    function FindById(Id: Integer): TCell;
    procedure GetCellsWithWeight(List: TCells; Low, High: Integer);
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
    procedure ClearMark;
    procedure ClearWeight;
    function ToString: ansistring; override;
  end;

  { TCellLink }

  TCellLink = class
    Points: array of TPoint;
    Cells: TCells;
    Map: TMap;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
    constructor Create;
    destructor Destroy; override;
  end;

  { TCellLinks }

  TCellLinks = class(TFPGObjectList<TCellLink>)
    Map: TMap;
    function FindByCells(Cell1, Cell2: TCell): TCellLink;
    function AddLink(Cell1, Cell2: TCell): TCellLink;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
  end;

  { TCellLinkParams }

  TCellLinkParams = class
    Cell1: TCell;
    Cell2: TCell;
    Distance: Double;
    Angle: Double;
  end;

  { TView }

  TView = class
  private
    FDestRect: TRect;
    FZoom: Double;
    procedure SetDestRect(AValue: TRect);
    procedure SetZoom(AValue: Double);
  public
    Game: TGame;
    SourceRect: TRect;
    FocusedCell: TCell;
    SelectedCell: TCell;
    procedure Clear;
    constructor Create;
    destructor Destroy; override;
    procedure SelectCell(Pos: TPoint; Player: TPlayer; ShiftState: TShiftState);
    procedure CenterMap;
    procedure CenterPlayerCity(Player: TPlayer);
    function CanvasToCellPos(Pos: TPoint): TPoint;
    function CellToCanvasPos(Pos: TPoint): TPoint;
    function CanvasToCellRect(Pos: TRect): TRect;
    function CellToCanvasRect(Pos: TRect): TRect;
    procedure Assign(Source: TView);
    property DestRect: TRect read FDestRect write SetDestRect;
    property Zoom: Double read FZoom write SetZoom;
  end;

  { TCanvasEx }

  TCanvasEx = class(TCanvas)
    class procedure TextOutEx(Canvas: TCanvas; X,Y: Integer; const Text: string; MovePen: Boolean = True);
    class procedure PolygonEx(Canvas: TCanvas; const Points: array of Classes.TPoint; Winding: Boolean);
  end;

  TMapShape = (msRectangle, msImage, msRounded);

  { TMapArea }

  TMapArea = class
    Id: Integer;
    Map: TMap;
    BridgeCount: Integer;
    Cells: TCells;
    procedure GetBorderCells(List: TCells);
    constructor Create;
    destructor Destroy; override;
  end;

  TMapAreas = class(TFPGObjectList<TMapArea>)
  end;

  { TMap }

  TMap = class
  private
    FSize: TPoint;
    function GetSize: TPoint; virtual;
    procedure PaintCell(Canvas: TCanvas; Pos: TPoint; Text: string; View: TView;
      Cell: TCell);
    procedure DrawArrow(Canvas: TCanvas; View: TView; Pos: TPoint; Angle: Double;
      Text: string);
    function SearchDifferentCellArea(List: TCells; SourceArea,
      DestArea: TMapArea): TCell;
    procedure SetSize(AValue: TPoint); virtual;
  protected
    FPixelRect: TRect;
    FNewCellId: Integer;
    function GetNewCellId: Integer; virtual;
    procedure SortNeighborsByAngle;
  public
    Game: TGame;
    MaxPower: Integer;
    DefaultCellSize: TPoint;
    Cells: TCells;
    Shape: TMapShape;
    Image: TImage;
    CellLinks: TCellLinks;
    Areas: TMapAreas;
    function IsOutsideShape(Coord: TPoint): Boolean; virtual;
    function IsCellsNeighbor(Cell1, Cell2: TCell): Boolean; virtual;
    function IsValidIndex(Index: TPoint): Boolean; virtual;
    procedure Assign(Source: TMap); virtual;
    procedure LoadFromFile(FileName: string); virtual;
    procedure SaveToFile(FileName: string); virtual;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
    function PosToCell(Pos: TPoint; View: TView): TCell; virtual;
    function CellToPos(Cell: TCell): TPoint; virtual;
    procedure Grow(APlayer: TPlayer); virtual;
    procedure ComputePlayerStats; virtual;
    procedure Generate; virtual;
    procedure BuildMapAreas;
    procedure BuildBridges;
    procedure MakeSymetric;
    procedure CreateLinks;
    procedure Clear;
    constructor Create; virtual;
    destructor Destroy; override;
    procedure Paint(Canvas: TCanvas; View: TView); virtual;
    function CalculatePixelRect: TRect; virtual;
    procedure ForEachCells(Method: TMethod); virtual;
    property Size: TPoint read GetSize write SetSize;
    property PixelRect: TRect read FPixelRect;
  end;

  TPlayerCells = class;
  TPlayerMap = class;

  { TPlayerCell }

  TPlayerCell = class
    Explored: Boolean;
    InVisibleRange: Boolean;
    MapCell: TCell;
    List: TPlayerCells;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
  end;

  { TPlayerCells }

  TPlayerCells = class(TFPGObjectList<TPlayerCell>)
    Map: TPlayerMap;
    function SearchCell(Cell: TCell): TPlayerCell;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
  end;

  { TPlayerMap }

  TPlayerMap = class
    Cells: TPlayerCells;
    Player: TPlayer;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
    procedure Update;
    constructor Create;
    destructor Destroy; override;
    procedure CheckVisibility;
    procedure Paint(Canvas: TCanvas; View: TView);
  end;

  { TGameTurnStat }

  TGameTurnStat = class
    OccupiedCells: Integer;
    Units: Integer;
    DiscoveredCells: Integer;
    Cities: Integer;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
  end;

  { TGameTurnStats }

  TGameTurnStats = class(TFPGObjectList<TGameTurnStat>)
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
  end;

  TPlayerMode = (pmHuman, pmComputer);
  TComputerAgressivity = (caLow, caMedium, caHigh);
  TComputer = class;
  TUnitMove = class;

  { TPlayer }

  TPlayer = class
  private
    FClient: TClient;
    FGame: TGame;
    FMode: TPlayerMode;
    procedure SetClient(AValue: TClient);
    procedure SetGame(AValue: TGame);
    procedure Attack(var AttackPower, DefendPower: Integer);
    procedure ClearMovesFromCell(Cell: TCell);
    procedure MoveAll;
    procedure ReduceMovesPower;
    procedure CheckCounterMove(Move: TUnitMove);
    procedure SetMode(AValue: TPlayerMode);
    function SetMove(CellFrom, CellTo: TCell; Power: Integer; Confirmation: Boolean = True): TUnitMove;
    procedure UpdateRepeatMoves;
    procedure RemoveEmptyUnitMoves;
  public
    Id: Integer;
    Name: string;
    Color: TColor;
    TotalUnits: Integer;
    TotalCells: Integer;
    TotalCities: Integer;
    TotalDiscovered: Integer;
    StartUnits: Integer;
    StartCell: TCell;
    PlayerMap: TPlayerMap;
    Defensive: Boolean;
    Agressivity: TComputerAgressivity;
    TurnStats: TGameTurnStats;
    Moves: TUnitMoves;
    Computer: TComputer;
    function IsAlive: Boolean;
    procedure Clear;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
    procedure Paint(Canvas: TCanvas; View: TView);
    constructor Create;
    destructor Destroy; override;
    procedure Assign(Source: TPlayer);
    procedure LoadConfig(Config: TXmlConfig; Path: string);
    procedure SaveConfig(Config: TXmlConfig; Path: string);
    property Game: TGame read FGame write SetGame;
    property Client: TClient read FClient write SetClient;
    property Mode: TPlayerMode read FMode write SetMode;
  end;

    { TComputer }

  TComputer = class
    Game: TGame;
    //Targets: TFPGObjectList<TPlayer>;
    CellProcessDirection: Boolean;
    Player: TPlayer;
    procedure AttackNeutral;
    procedure AttackPlayers;
    procedure InnerMoves;
    procedure IncreaseMoves;
    procedure Process;
    procedure FallBack;
    function AttackersCount(Cell: TCell): Integer;
  end;

  TPlayerArray = array of TPlayer;

  { TPlayers }

  TPlayers = class(TFPGObjectList<TPlayer>)
  public
    Game: TGame;
    NewPlayerId: Integer;
    function GetAliveCount: Integer;
    function FindById(Id: Integer): TPlayer;
    procedure New(Name: string; Color: TColor; Mode: TPlayerMode);
    function GetNewPlayerId: Integer;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
    constructor Create;
    function GetFirstHuman: TPlayer;
    procedure Assign(Source: TPlayers);
    procedure LoadConfig(Config: TXmlConfig; Path: string);
    procedure SaveConfig(Config: TXmlConfig; Path: string);
    function GetAlivePlayers: TPlayerArray;
    function GetAlivePlayersWithCities: TPlayerArray;
  end;

  { TUnitMove }

  TUnitMove = class
  private
    FCellFrom: TCell;
    FCellTo: TCell;
    procedure SetCellFrom(AValue: TCell);
    procedure SetCellTo(AValue: TCell);
  public
    List: TUnitMoves;
    CountOnce: Integer;
    CountRepeat: Integer;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
    constructor Create;
    destructor Destroy; override;
    property CellFrom: TCell read FCellFrom write SetCellFrom;
    property CellTo: TCell read FCellTo write SetCellTo;
  end;

  { TUnitMoves }

  TUnitMoves = class(TFPGObjectList<TUnitMove>)
    Game: TGame;
    function SearchByFromTo(CellFrom, CellTo: TCell): TUnitMove;
    procedure LoadFromNode(Node: TDOMNode);
    procedure SaveToNode(Node: TDOMNode);
  end;

  { TClient }

  TClient = class
  private
    FGame: TGame;
    FControlPlayer: TPlayer;
    procedure SetControlPlayer(AValue: TPlayer);
    procedure SetGame(AValue: TGame);
  public
    Name: string;
    View: TView;
    constructor Create;
    destructor Destroy; override;
    property ControlPlayer: TPlayer read FControlPlayer write SetControlPlayer;
    property Game: TGame read FGame write SetGame;
  end;

  { TClients }

  TClients = class(TFPGObjectList<TClient>)
    Game: TGame;
    procedure New(Name: string);
  end;

  { TGame }

  TMoveEvent = procedure(CellFrom, CellTo: TCell; var CountOnce, CountRepeat: Integer;
    Update: Boolean; var Confirm: Boolean) of object;
  TMoveUpdatedEvent = procedure(UnitMove: TUnitMove) of object;

  TWinEvent = procedure(Player: TPlayer) of object;
  TGrowAmount = (gaByOne, gaBySquareRoot);
  TGrowCells = (gcNone, gcPlayerCities, gcPlayerAll);
  TMapType = (mtNone, mtHexagon, mtSquare, mtTriangle, mtVoronoi);
  TWinObjective = (woDefeatAllOponents, woDefeatAllOponentsCities,
    woSpecialCaptureCell, woStayAliveForDefinedTurns);

  TGame = class
  private
    FMapType: TMapType;
    FOnMove: TMoveEvent;
    FOnMoveUpdated: TMoveUpdatedEvent;
    FOnNewTurn: TNotifyEvent;
    FOnPlayerChange: TNotifyEvent;
    FOnWin: TWinEvent;
    FRunning: Boolean;
    LoadedImageFileName: string;
    ProbabilityMatrix: array of array of Single;
    procedure RecordTurnStats;
    procedure SetMapType(AValue: TMapType);
    procedure SetRunning(AValue: Boolean);
    procedure BuildTerrain;
    procedure InitClients;
    procedure SelectPlayerStartCell(Player: TPlayer);
    procedure CalculatePlayersDistance;
    procedure PropagatePlayerDistance(List: TCells);
    procedure InitDefaultPlayersSetting;
  public
    DevelMode: Boolean;
    Players: TPlayers;
    PlayersSetting: TPlayers;
    Clients: TClients;
    Map: TMap;
    MapImageFileName: string;
    VoidEnabled: Boolean;
    VoidPercentage: Integer;
    SymetricMap: Boolean;
    GrowCells: TGrowCells;
    GrowAmount: TGrowAmount;
    CityEnabled: Boolean;
    CityPercentage: Integer;
    CurrentPlayer: TPlayer;
    TurnCounter: Integer;
    WinObjective: TWinObjective;
    SpecialCaptureCell: TCell;
    StayAliveForDefinedTurns: Integer;
    MaxNeutralUnits: Integer;
    FileName: string;
    FogOfWar: Boolean;
    BridgeEnabled: Boolean;
    function AttackProbability(AttackCount, DefendCount: Integer): Double;
    procedure LoadConfig(Config: TXmlConfig; Path: string);
    procedure SaveConfig(Config: TXmlConfig; Path: string);
    procedure LoadFromFile(FileName: string);
    procedure SaveToFile(FileName: string);
    procedure ComputePlayerStats;
    procedure NextTurn;
    procedure CheckWinObjective;
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    procedure New;
    procedure EndGame(Winner: TPlayer = nil);
    property Running: Boolean read FRunning write SetRunning;
    property MapType: TMapType read FMapType write SetMapType;
  published
    property OnMove: TMoveEvent read FOnMove write FOnMove;
    property OnMoveUpdated: TMoveUpdatedEvent read FOnMoveUpdated write FOnMoveUpdated;
    property OnWin: TWinEvent read FOnWin write FOnWin;
    property OnNewTurn: TNotifyEvent read FOnNewTurn write FOnNewTurn;
    property OnPlayerChange: TNotifyEvent read FOnPlayerChange write FOnPlayerChange;
  end;

var
  PlayerModeText: array[TPlayerMode] of string;

const
  clOrange = $009Aff;
  PlayerColors: array[0..7] of TColor = (clBlue, clRed, clGreen, clOrange,
    clPurple, clMaroon, clAqua, clFuchsia);
  ComputerAggroProbability: array[TComputerAgressivity] of Single = (0.9, 0.7, 0.5);

procedure InitStrings;

resourcestring
  SPlayer = 'Player';


implementation

uses
  UMap;

resourcestring
  SMinimumPlayers = 'You need at least two players';
  SHuman = 'Human';
  SComputer = 'Computer';
  SWrongFileFormat = 'Wrong file format';
  SUnfinishedBattle = 'Unfinished battle';
  SNewGameFile = 'New game.xtg';
  SZeroZoomNotAlowed = 'Zero zoom not allowed';
  SCellRemoveNeighborError = 'Can''t remove cell from neighbour cell';
  SNegativeCellPowerNotAllowed = 'Not allowed to substract power under zero to negative value';
  SAttackerPowerPositive = 'Attacker power have to be higher then 0.';
  SDefenderPowerPositive = 'Defender power have to be higher then or equal to 0.';
  SUnitPowerMismatch = 'Unit move power mismatch. Cell power is %d but %d moved away.';

procedure InitStrings;
begin
  PlayerModeText[pmHuman] := SHuman;
  PlayerModeText[pmComputer] := SComputer;
end;

function HalfColor(Color: TColor): TColor;
begin
  Result :=
    ((((Color shr 0) and $ff) shr 1) shl 0) or
    ((((Color shr 8) and $ff) shr 1) shl 8) or
    ((((Color shr 16) and $ff) shr 1) shl 16) or
    ((((Color shr 24) and $ff) shr 0) shl 24);
end;

{ TGameTurnStat }

procedure TGameTurnStat.LoadFromNode(Node: TDOMNode);
begin
  OccupiedCells := ReadInteger(Node, 'OccupiedCells', 0);
  Units := ReadInteger(Node, 'Units', 0);
  DiscoveredCells := ReadInteger(Node, 'DiscoveredCells', 0);
  Cities := ReadInteger(Node, 'Cities', 0);
end;

procedure TGameTurnStat.SaveToNode(Node: TDOMNode);
begin
  WriteInteger(Node, 'OccupiedCells', OccupiedCells);
  WriteInteger(Node, 'Units', Units);
  WriteInteger(Node, 'DiscoveredCells', DiscoveredCells);
  WriteInteger(Node, 'Cities', Cities);
end;

{ TGameTurnStats }

procedure TGameTurnStats.LoadFromNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  NewTurnStat: TGameTurnStat;
begin
  Count := 0;
  Node2 := Node.FirstChild;
  while Assigned(Node2) and (Node2.NodeName = 'TurnStat') do begin
    NewTurnStat := TGameTurnStat.Create;
    NewTurnStat.LoadFromNode(Node2);
    Add(NewTurnStat);
    Node2 := Node2.NextSibling;
  end;
end;

procedure TGameTurnStats.SaveToNode(Node: TDOMNode);
var
  I: Integer;
  NewNode: TDOMNode;
begin
  for I := 0 to Count - 1 do begin;
    NewNode := Node.OwnerDocument.CreateElement('TurnStat');
    Node.AppendChild(NewNode);
    TGameTurnStat(Items[I]).SaveToNode(NewNode);
  end;
end;

{ TClients }

procedure TClients.New(Name: string);
var
  NewClient: TClient;
begin
  NewClient := TClient.Create;
  NewClient.Game := Game;
  NewClient.Name := Name;
  Add(NewClient);
end;

{ TClient }

procedure TClient.SetGame(AValue: TGame);
begin
  if FGame = AValue then Exit;
  FGame := AValue;
  View.Game := AValue;
end;

procedure TClient.SetControlPlayer(AValue: TPlayer);
begin
  if FControlPlayer = AValue then Exit;
  if Assigned(FControlPlayer) then
    FControlPlayer.FClient := nil;
  FControlPlayer := AValue;
  if Assigned(FControlPlayer) then
     FControlPlayer.FClient := Self;
end;

constructor TClient.Create;
begin
  View := TView.Create;
end;

destructor TClient.Destroy;
begin
  ControlPlayer := nil;
  FreeAndNil(View);
  inherited Destroy;
end;

{ TCellLink }

procedure TCellLink.LoadFromNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  Node3: TDOMNode;
begin
  Node3 := Node.FindNode('Points');
  if Assigned(Node3) then begin
    SetLength(Points, 0);
    Node2 := Node3.FirstChild;
    while Assigned(Node2) and (Node2.NodeName = 'Point') do begin
      SetLength(Points, Length(Points) + 1);
      Points[High(Points)].X := ReadInteger(Node2, 'X', 0);
      Points[High(Points)].Y := ReadInteger(Node2, 'Y', 0);
      Node2 := Node2.NextSibling;
    end;
  end;
end;

procedure TCellLink.SaveToNode(Node: TDOMNode);
var
  NewNode: TDOMNode;
  NewNode2: TDOMNode;
  I: Integer;
begin
  NewNode := Node.OwnerDocument.CreateElement('Points');
  Node.AppendChild(NewNode);
  for I := 0 to Length(Points) - 1 do begin
    NewNode2 := NewNode.OwnerDocument.CreateElement('Point');
    NewNode.AppendChild(NewNode2);
    WriteInteger(NewNode2, 'X', Points[I].X);
    WriteInteger(NewNode2, 'Y', Points[I].Y);
  end;
end;

constructor TCellLink.Create;
begin
  Cells := TCells.Create;
  Cells.FreeObjects := False;
end;

destructor TCellLink.Destroy;
var
  I: Integer;
begin
  for I := 0 to Cells.Count - 1 do begin
    if TCell(Cells[I]).Neighbors.Remove(TCell(Cells[1 - I])) = -1 then
      raise Exception.Create(SCellRemoveNeighborError);
    if TCell(Cells[I]).Links.Remove(Self) = -1 then
      raise Exception.Create(SCellRemoveNeighborError);
  end;
  FreeAndNil(Cells);
  inherited Destroy;
end;

{ TCellLinks }

function TCellLinks.FindByCells(Cell1, Cell2: TCell): TCellLink;
var
  I: Integer;
begin
  I := 0;
  while (I < Count) do begin
    if ((TCellLink(Items[I]).Cells[0] = Cell1) and (TCellLink(Items[I]).Cells[1] = Cell2)) or
    ((TCellLink(Items[I]).Cells[0] = Cell2) and (TCellLink(Items[I]).Cells[1] = Cell1)) then
      Break;
    Inc(I);
  end;
  if I < Count then Result := TCellLink(Items[I])
    else Result := nil;
end;

function TCellLinks.AddLink(Cell1, Cell2: TCell): TCellLink;
begin
  Result := TCellLink.Create;
  Cell1.Neighbors.Add(Cell2);
  Cell1.Links.Add(Result);
  Cell2.Neighbors.Add(Cell1);
  Cell2.Links.Add(Result);
  SetLength(Result.Points, 2);
  Result.Cells.Add(Cell1);
  Result.Points[0] := Cell1.PosPx;
  Result.Cells.Add(Cell2);
  Result.Points[1] := Cell2.PosPx;
  Result.Map := Map;
  Map.CellLinks.Add(Result);
end;

procedure TCellLinks.LoadFromNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  NewCell: TCellLink;
begin
  Count := 0;
  Node2 := Node.FirstChild;
  while Assigned(Node2) and (Node2.NodeName = 'CellLink') do begin
    NewCell := TCellLink.Create;
    //NewCell.Map := Map;
    NewCell.LoadFromNode(Node2);
    Add(NewCell);
    Node2 := Node2.NextSibling;
  end;
end;

procedure TCellLinks.SaveToNode(Node: TDOMNode);
var
  I: Integer;
  NewNode2: TDOMNode;
begin
  for I := 0 to Count - 1 do
  with TCellLink(Items[I]) do begin
    NewNode2 := Node.OwnerDocument.CreateElement('CellLink');
    Node.AppendChild(NewNode2);
    SaveToNode(NewNode2);
  end;
end;

{ TMapArea }

procedure TMapArea.GetBorderCells(List: TCells);
var
  Cell: TCell;
begin
  List.Clear;
  Map.Cells.ClearMark;
  for Cell in Cells do begin
    if Cell.NeighboringToVoid and (Cell.Area = Self) and (not Cell.Mark) then begin
      List.Add(Cell);
      Cell.Mark := True;
    end;
  end;
end;

constructor TMapArea.Create;
begin
  Cells := TCells.Create;
  Cells.FreeObjects := False;
end;

destructor TMapArea.Destroy;
begin
  FreeAndNil(Cells);
  inherited Destroy;
end;

{ TPlayerCell }

procedure TPlayerCell.LoadFromNode(Node: TDOMNode);
begin
  Explored := ReadBoolean(Node, 'Explored', False);
  MapCell := List.Map.Player.Game.Map.Cells.FindById(ReadInteger(Node, 'MapCell', 0));
end;

procedure TPlayerCell.SaveToNode(Node: TDOMNode);
begin
  WriteBoolean(Node, 'Explored', Explored);
  WriteInteger(Node, 'MapCell', MapCell.Id);
end;

{ TPlayerCells }

function TPlayerCells.SearchCell(Cell: TCell): TPlayerCell;
var
  I: Integer;
begin
  I := 0;
  while (I < Count) and (TPlayerCell(Items[I]).MapCell <> Cell) do Inc(I);
  if I < Count then Result := TPlayerCell(Items[I])
    else Result := nil;
end;

procedure TPlayerCells.LoadFromNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  NewCell: TPlayerCell;
begin
  Count := 0;
  Node2 := Node.FirstChild;
  while Assigned(Node2) and (Node2.NodeName = 'Cell') do begin
    NewCell := TPlayerCell.Create;
    NewCell.List := Self;
    NewCell.LoadFromNode(Node2);
    Add(NewCell);
    Node2 := Node2.NextSibling;
  end;
end;

procedure TPlayerCells.SaveToNode(Node: TDOMNode);
var
  I: Integer;
  NewNode: TDOMNode;
begin
  for I := 0 to Count - 1 do begin;
    NewNode := Node.OwnerDocument.CreateElement('Cell');
    Node.AppendChild(NewNode);
    TPlayerCell(Items[I]).SaveToNode(NewNode);
  end;
end;

{ TPlayerMap }

procedure TPlayerMap.LoadFromNode(Node: TDOMNode);
var
  NewNode: TDOMNode;
begin
  with Node do begin
    NewNode := FindNode('Cells');
    if Assigned(NewNode) then
      Cells.LoadFromNode(NewNode);
  end;
end;

procedure TPlayerMap.SaveToNode(Node: TDOMNode);
var
  NewNode: TDOMNode;
begin
  with Node do begin
    NewNode := OwnerDocument.CreateElement('Cells');
    AppendChild(NewNode);
    Cells.SaveToNode(NewNode);
  end;
end;

procedure TPlayerMap.Update;
var
  I: Integer;
  OldCount: Integer;
begin
  // Update players cells count to map cells count to be 1:1
  OldCount := Cells.Count;
  Cells.Count := Player.Game.Map.Cells.Count;
  for I := OldCount to Cells.Count - 1 do
    Cells[I] := TPlayerCell.Create;

  for I := 0 to Cells.Count - 1 do
  with TPlayerCell(Cells[I]) do begin
    List := Cells;
    Explored := False;
    InVisibleRange := False;
    MapCell := TCell(Player.Game.Map.Cells[I]);
  end;
end;

constructor TPlayerMap.Create;
begin
  Cells := TPlayerCells.Create;
  Cells.Map := Self;
end;

destructor TPlayerMap.Destroy;
begin
  FreeAndNil(Cells);
  inherited Destroy;
end;

procedure TPlayerMap.CheckVisibility;
var
  I: Integer;
  C: Integer;
  NeighCount: Integer;
begin
  for I := 0 to Cells.Count - 1 do
  with TPlayerCell(Cells[I]) do begin
    NeighCount := 0;
    for C := 0 to MapCell.Neighbors.Count - 1 do
      if TCell(MapCell.Neighbors[C]).Player = Player then
        Inc(NeighCount);

    InVisibleRange := (NeighCount > 0) or (TCell(MapCell).Player = Player);
    if InVisibleRange and not Explored then Explored := True;
  end;
end;

procedure TPlayerMap.Paint(Canvas: TCanvas; View: TView);
var
  I: Integer;
  Cell: TPlayerCell;
  PosFrom, PosTo: TPoint;
  Angle: Double;
  ArrowCenter: TPoint;
  Move: TUnitMove;
  CellText: string;
  CellLink: TCellLink;
  NeighCell: TCell;
begin
  with Canvas, View do
  try
    Lock;
    // Draw cell links
    Pen.Color := clBlack;
    Pen.Style := psSolid;
    Pen.Width := 3;
    for CellLink in Player.Game.Map.CellLinks do
    with CellLink do begin
      if Length(Points) >= 2 then begin
        MoveTo(PointToStdPoint(View.CellToCanvasPos(Points[0])));
        for I := 1 to Length(Points) - 1 do
          LineTo(PointToStdPoint(View.CellToCanvasPos(Points[I])));
      end;
    end;

    // Draw cells
    for Cell in Cells do begin
      if (Cell.MapCell.Terrain <> ttVoid) and Cell.MapCell.IsVisible(View) then begin
        if Cell.MapCell.Player = Player then
          CellText := IntToStr(Cell.MapCell.GetAvialPower)
          else CellText := IntToStr(Cell.MapCell.Power);
        if Assigned(SelectedCell) and (SelectedCell = Cell.MapCell) then
            Brush.Color := clGreen
          else if Assigned(SelectedCell) and Player.Game.Map.IsCellsNeighbor(SelectedCell, Cell.MapCell) then
            Brush.Color := clPurple
          else if Player.Game.FogOfWar then begin
            if Cell.InVisibleRange then begin
              Brush.Color := Cell.MapCell.GetColor;
            end else begin
              if Cell.Explored then begin
                Brush.Color := $404040;
                CellText := '';
              end else begin
                Brush.Color := clBlack;
                CellText := '';
              end;
            end;
          end else Brush.Color := Cell.MapCell.GetColor;
        Player.Game.Map.PaintCell(Canvas, Cell.MapCell.PosPx, CellText, View, Cell.MapCell);
      end else
      if Game.FogOfWar and (Cell.MapCell.Terrain = ttVoid) and (not Cell.Explored) then begin
        Brush.Color := clBlack;
        Player.Game.Map.PaintCell(Canvas, Cell.MapCell.PosPx, '', View, Cell.MapCell);
      end;
    end;

    // Draw links to neighbors
    if Player.Game.DevelMode then
    for Cell in Cells do begin
      for NeighCell in Cell.MapCell.Neighbors do begin
        Pen.Color := clYellow;
        MoveTo(PointToStdPoint(View.CellToCanvasPos(Cell.MapCell.PosPx)));
        LineTo(PointToStdPoint(View.CellToCanvasPos(NeighCell.PosPx)));
      end;

      Font.Color := clRed;
      Brush.Style := bsClear;
      TextOut(View.CellToCanvasPos(Cell.MapCell.PosPx).X,
        View.CellToCanvasPos(Cell.MapCell.PosPx).Y, IntToStr(Cell.MapCell.Id));
    end;

    // Draw arrows
    Pen.Color := clCream;
    for Move in Player.Moves do begin
      PosFrom := Player.Game.Map.CellToPos(Move.CellFrom);
      PosTo := Player.Game.Map.CellToPos(Move.CellTo);
      // In Fog of war mode show only
      if Game.FogOfWar and not Cells.SearchCell(Move.CellFrom).InVisibleRange and
        not Cells.SearchCell(Move.CellTo).InVisibleRange then
        Continue;
      if Move.CountRepeat > 0 then Pen.Width := 2
        else Pen.Width := 1;
      Angle := ArcTan((PosTo.Y - PosFrom.Y) / (PosTo.X - PosFrom.X));
      if Sign(PosTo.X - PosFrom.X) = -1 then Angle := Angle + Pi;
      ArrowCenter := View.CellToCanvasPos(TPoint.Create(Trunc(PosFrom.X + (PosTo.X - PosFrom.X) / 2),
        Trunc(PosFrom.Y + (PosTo.Y - PosFrom.Y) / 2)));
      Player.Game.Map.DrawArrow(Canvas, View, ArrowCenter,
        Angle, IntToStr(Move.CountOnce));
    end;
  finally
    Unlock;
  end;
end;

{ TCanvasEx }

class procedure TCanvasEx.TextOutEx(Canvas: TCanvas; X, Y: Integer; const Text: string;
  MovePen: Boolean);
var
  Flags : Cardinal;
begin
  with Canvas do begin
    Changing;
    RequiredState([csHandleValid, csFontValid, csBrushValid]);
    Flags := 0;
    if TextStyle.Opaque then
      Flags := ETO_Opaque;
    ExtUTF8Out(Handle, X, Y, Flags, nil, PChar(Text), Length(Text), nil);
    if MovePen then MoveTo(X + TextWidth(Text), Y);
    Changed;
  end;
end;

class procedure TCanvasEx.PolygonEx(Canvas: TCanvas; const Points: array of Classes.TPoint; Winding: Boolean);
begin
  //Changing;
  //RequiredState([csHandleValid, csBrushValid, csPenValid]);
  //Canvas.Brush.Style := bsClear;
  LCLIntf.Polygon(Canvas.Handle, @Points[0], Length(Points), Winding);
//  SetLength(Points, Length(Points) + 1);
//  Points[Length(Points) - 1] = Points[0];
//  LCLIntf.Polyline(Canvas.Handle, @Points[0], Length(Points));
  //Changed;
end;


{ TCells }

procedure TCells.FixRefId;
var
  I: Integer;
begin
  for I := 0 to Count - 1 do
  TCell(Items[I]).FixRefId;
end;

function TCells.FindById(Id: Integer): TCell;
var
  I: Integer;
begin
  I := 0;
  while (I < Count) and (TCell(Items[I]).Id <> Id) do Inc(I);
  if I < Count then Result := TCell(Items[I])
    else Result := nil;
end;

procedure TCells.GetCellsWithWeight(List: TCells; Low, High: Integer);
var
  Cell: TCell;
begin
  List.Clear;
  for Cell in Self do
    if (Cell.Terrain <> ttVoid) and (Cell.Weight >= Low) and
      (Cell.Weight <= High) then List.Add(Cell);
end;

procedure TCells.LoadFromNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  NewCell: TCell;
begin
  Count := 0;
  Node2 := Node.FirstChild;
  while Assigned(Node2) and (Node2.NodeName = 'Cell') do begin
    NewCell := TCell.Create;
    NewCell.Map := Map;
    NewCell.LoadFromNode(Node2);
    Add(NewCell);
    Node2 := Node2.NextSibling;
  end;
end;

procedure TCells.SaveToNode(Node: TDOMNode);
var
  I: Integer;
  NewNode2: TDOMNode;
begin
  for I := 0 to Count - 1 do
  with TCell(Items[I]) do begin
    NewNode2 := Node.OwnerDocument.CreateElement('Cell');
    Node.AppendChild(NewNode2);
    SaveToNode(NewNode2);
  end;
end;

procedure TCells.ClearMark;
var
  Cell: TCell;
begin
  for Cell in Self do Cell.Mark := False;
end;

procedure TCells.ClearWeight;
var
  Cell: TCell;
begin
  for Cell in Self do Cell.Weight := 0;
end;

function TCells.ToString: ansistring;
var
  C: TCell;
begin
  Result := '';
  for C in Self do
    Result := Result + IntToStr(C.Id) + ', ';
end;

{ TPlayers }

function TPlayers.GetAliveCount: Integer;
var
  Player: TPlayer;
begin
  Result := 0;
  for Player in Self do
    if Player.IsAlive then Inc(Result);
end;

function TPlayers.FindById(Id: Integer): TPlayer;
var
  I: Integer;
begin
  I := 0;
  while (I < Count) and (TPlayer(Items[I]).Id <> Id) do Inc(I);
  if I < Count then Result := TPlayer(Items[I])
    else Result := nil;
end;

procedure TPlayers.New(Name: string; Color: TColor; Mode: TPlayerMode);
var
  NewPlayer: TPlayer;
begin
  NewPlayer := TPlayer.Create;
  NewPlayer.Game := Game;
  NewPlayer.Name := Name;
  NewPlayer.Color := Color;
  NewPlayer.Mode := Mode;
  NewPlayer.Id := GetNewPlayerId;
  if Mode = pmComputer then
    NewPlayer.Agressivity := caMedium;
  Add(NewPlayer);
end;

function TPlayers.GetNewPlayerId: Integer;
begin
  Result := NewPlayerId;
  Inc(NewPlayerId);
end;

procedure TPlayers.LoadFromNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  NewPlayer: TPlayer;
begin
  Count := 0;
  Node2 := Node.FirstChild;
  while Assigned(Node2) and (Node2.NodeName = 'Player') do begin
    NewPlayer := TPlayer.Create;
    NewPlayer.Game := Game;
    NewPlayer.LoadFromNode(Node2);
    Add(NewPlayer);
    Node2 := Node2.NextSibling;
  end;
end;

procedure TPlayers.SaveToNode(Node: TDOMNode);
var
  I: Integer;
  NewNode: TDOMNode;
begin
  for I := 0 to Count - 1 do begin;
    NewNode := Node.OwnerDocument.CreateElement('Player');
    Node.AppendChild(NewNode);
    TPlayer(Items[I]).SaveToNode(NewNode);
  end;
end;

constructor TPlayers.Create;
begin
  inherited Create;
  NewPlayerId := 1;
end;

function TPlayers.GetFirstHuman: TPlayer;
var
  I: Integer;
begin
  I := 0;
  while (I < Count) and (TPlayer(Items[I]).Mode <> pmHuman) do Inc(I);
  if I < Count then Result := TPlayer(Items[I])
    else Result := nil;
end;

procedure TPlayers.Assign(Source: TPlayers);
var
  I: Integer;
begin
  while Count > Source.Count do
    Delete(Count - 1);
  while Count < Source.Count do
    Add(TPlayer.Create);
  for I := 0 to Count - 1 do begin
    TPlayer(Items[I]).Assign(TPlayer(Source[I]));
    TPlayer(Items[I]).Game := Game;
  end;
  NewPlayerId := Source.NewPlayerId;
end;

procedure TPlayers.LoadConfig(Config: TXmlConfig; Path: string);
var
  I: Integer;
  NewCount: Integer;
begin
  with Config do begin
    NewCount := GetValue(DOMString(Path + '/Count'), -1);
    NewPlayerId := 1;
    if NewCount >= 2 then begin
      Self.Clear;
      Count := NewCount;
      for I := 0 to Count - 1 do begin
        Items[I] := TPlayer.Create;
        TPlayer(Items[I]).Id := GetNewPlayerId;
        TPlayer(Items[I]).Game := Game;
        TPlayer(Items[I]).LoadConfig(Config, Path + '/Player' + IntToStr(I));
      end;
    end;
  end;
end;

procedure TPlayers.SaveConfig(Config: TXmlConfig; Path: string);
var
  I: Integer;
begin
  for I := 0 to Count - 1 do
    TPlayer(Items[I]).SaveConfig(Config, Path + '/Player' + IntToStr(I));
  with Config do begin
    SetValue(DOMString(Path + '/Count'), Count);
  end;
end;

{ TUnitMoves }

function TUnitMoves.SearchByFromTo(CellFrom, CellTo: TCell): TUnitMove;
var
  UnitMove: TUnitMove;
begin
  Result := nil;
  for UnitMove in Self do
  if (UnitMove.CellFrom = CellFrom) and (UnitMove.CellTo = CellTo) then begin
    Result := UnitMove;
    Break;
  end;
end;

procedure TUnitMoves.LoadFromNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  NewUnitMove: TUnitMove;
begin
  Count := 0;
  Node2 := Node.FirstChild;
  while Assigned(Node2) and (Node2.NodeName = 'UnitMove') do begin
    NewUnitMove := TUnitMove.Create;
    NewUnitMove.List := Self;
    NewUnitMove.LoadFromNode(Node2);
    Add(NewUnitMove);
    Node2 := Node2.NextSibling;
  end;
end;

procedure TUnitMoves.SaveToNode(Node: TDOMNode);
var
  I: Integer;
  NewNode: TDOMNode;
begin
  for I := 0 to Count - 1 do begin;
    NewNode := Node.OwnerDocument.CreateElement('UnitMove');
    Node.AppendChild(NewNode);
    TUnitMove(Items[I]).SaveToNode(NewNode);
  end;
end;

{ TMap }

function TMap.GetSize: TPoint;
begin
  Result:= FSize;
end;

procedure TMap.SetSize(AValue: TPoint);
begin
  if (FSize.X <> AValue.X) or (FSize.Y <> AValue.Y) then begin
    FSize := AValue;
  end;
end;

function CompareCellAngle(const C1, C2: TCell): Integer;
begin
  if C1.Angle < C2.Angle then Result := -1
  else if C1.Angle > C2.Angle then Result := 1
  else Result := 0;
end;

procedure TMap.SortNeighborsByAngle;
var
  Cell: TCell;
  NeighborCell: TCell;
begin
  for Cell in Cells do begin
    for NeighborCell in Cell.Neighbors do
      NeighborCell.Angle := TLine.Create(Cell.PosPx, NeighborCell.PosPx).GetAngle;

    Cell.Neighbors.Sort(CompareCellAngle);
  end;
end;

function TMap.GetNewCellId: Integer;
begin
  Result := FNewCellId;
  Inc(FNewCellId);
end;

function TMap.IsOutsideShape(Coord: TPoint): Boolean;
var
  Rect: TRect;
  Color: TColor;
  Pos: TPoint;
  Center: TPoint;
begin
  case Shape of
    msRectangle: Result := False;
    msImage: begin
      Rect := PixelRect;
      with Image.Picture.Bitmap do begin
        Pos := TPoint.Create(Trunc(Coord.X / Rect.Size.X * Width),
          Trunc(Coord.Y / Rect.Size.Y * Height));
        Color := Canvas.Pixels[Pos.X, Pos.Y];
      end;
      Result := Color <> clWhite;
    end;
    msRounded: begin
      Rect := PixelRect;
      Center := Rect.Center;
      Result := Sqr(Coord.X - Center.X) / Sqr(Rect.Size.X div 2) +
        Sqr(Coord.Y - Center.Y) / Sqr(Rect.Size.Y div 2) > 1;
    end
    else Result := False;
  end;
end;

procedure TMap.DrawArrow(Canvas: TCanvas; View: TView; Pos: TPoint;
  Angle: Double; Text: string);
var
  Points: array of Classes.TPoint;
  FPoints: array of TPointF;
  I: Integer;
  ArrowSize: TPoint;
begin
  Canvas.Brush.Color := clWhite;
  Canvas.Pen.Color := clBlack;
  SetLength(Points, 8);
  SetLength(FPoints, 8);
  ArrowSize := TPoint.Create(Trunc(DefaultCellSize.X / 3 * View.Zoom),
    Trunc(DefaultCellSize.Y / 3 * View.Zoom));
  FPoints[0] := TPointF.Create(+0.5 * ArrowSize.X, +0 * ArrowSize.Y);
  FPoints[1] := TPointF.Create(+0 * ArrowSize.X, +0.5 * ArrowSize.Y);
  FPoints[2] := TPointF.Create(+0 * ArrowSize.X, +0.25 * ArrowSize.Y);
  FPoints[3] := TPointF.Create(-0.5 * ArrowSize.X, +0.25 * ArrowSize.Y);
  FPoints[4] := TPointF.Create(-0.5 * ArrowSize.X, -0.25 * ArrowSize.Y);
  FPoints[5] := TPointF.Create(+0 * ArrowSize.X, -0.25 * ArrowSize.Y);
  FPoints[6] := TPointF.Create(+0 * ArrowSize.X, -0.5 * ArrowSize.Y);
  FPoints[7] := TPointF.Create(+0.5 * ArrowSize.X, 0 * ArrowSize.Y);
  // Rotate
  for I := 0 to Length(Points) - 1 do
    FPoints[I] := TPointF.Create(FPoints[I].X * Cos(Angle) - FPoints[I].Y * Sin(Angle),
      FPoints[I].X * Sin(Angle) + FPoints[I].Y * Cos(Angle));
  // Shift
  for I := 0 to Length(Points) - 1 do
    Points[I] := Point(Trunc(FPoints[I].X + Pos.X), Trunc(FPoints[I].Y + Pos.Y));
  with Canvas do begin
    Brush.Style := bsSolid;
    Polygon(Points);
    Brush.Style := bsClear;
    Font.Color := clBlack;
    Font.Size := Trunc(26 * View.Zoom);
    TextOut(Pos.X - TextWidth(Text) div 2,
      Pos.Y - TextHeight(Text) div 2, Text);
    Pen.Width := 1;
  end;
end;

function TMap.IsCellsNeighbor(Cell1, Cell2: TCell): Boolean;
begin
  Result := Cell1.Neighbors.IndexOf(Cell2) <> -1;
end;

function TMap.IsValidIndex(Index: TPoint): Boolean;
begin
  Result := False;
end;

procedure TMap.Assign(Source: TMap);
//var
//  I: Integer;
begin
  MaxPower := Source.MaxPower;
  Game := Source.Game;
  Size := Source.Size;
  DefaultCellSize := Source.DefaultCellSize;
  Shape := Source.Shape;
  Image.Picture.Bitmap.Assign(Source.Image.Picture.Bitmap);

  // TODO: How to copy cells
  {// Copy all cells
  Cells.Count := 0;
  Cells.Count := Source.Cells.Count;
  for I := 0 to Cells.Count - 1 do begin
    Cells[I] := TCell.Create;
    TCell(Cells[I]).Map := Self;
    TCell(Cells[I]).Assign(TCell(Source.Cells[I]));
  end;
  }
end;

procedure TMap.LoadFromFile(FileName: string);
begin

end;

procedure TMap.SaveToFile(FileName: string);
begin

end;

procedure TMap.LoadFromNode(Node: TDOMNode);
var
  Node2: TDOMNode;
begin
  Size := TPoint.Create(ReadInteger(Node, 'SizeX', 0), ReadInteger(Node, 'SizeY', 0));
  DefaultCellSize.X := ReadInteger(Node, 'DefaultCellSizeX', 1);
  DefaultCellSize.Y := ReadInteger(Node, 'DefaultCellSizeY', 1);
  MaxPower := ReadInteger(Node, 'MaxPower', 99);
  Shape := TMapShape(ReadInteger(Node, 'Shape', Integer(msRectangle)));
  Node2 := Node.FindNode('Cells');
  if Assigned(Node2) then
    Cells.LoadFromNode(Node2);
  Node2 := Node.FindNode('CellLinks');
  if Assigned(Node2) then
    CellLinks.LoadFromNode(Node2);
end;

procedure TMap.SaveToNode(Node: TDOMNode);
var
  NewNode: TDOMNode;
begin
  WriteInteger(Node, 'DefaultCellSizeX', DefaultCellSize.X);
  WriteInteger(Node, 'DefaultCellSizeY', DefaultCellSize.Y);
  WriteInteger(Node, 'MaxPower', MaxPower);
  WriteInteger(Node, 'Shape', Integer(Shape));
  WriteInteger(Node, 'SizeX', Size.X);
  WriteInteger(Node, 'SizeY', Size.Y);
  NewNode := Node.OwnerDocument.CreateElement('Cells');
  Node.AppendChild(NewNode);
  Cells.SaveToNode(NewNode);
  NewNode := Node.OwnerDocument.CreateElement('CellLinks');
  Node.AppendChild(NewNode);
  CellLinks.SaveToNode(NewNode);
end;

function TMap.PosToCell(Pos: TPoint; View: TView): TCell;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to Cells.Count - 1 do
  if TCell(Cells[I]).Terrain <> ttVoid then begin
    if TCell(Cells[I]).Polygon.IsPointInside(Pos) then begin
      Result := TCell(Cells[I]);
      Exit;
    end;
  end;
end;

function TMap.CellToPos(Cell: TCell): TPoint;
begin
  Result := Cell.PosPx;
end;

procedure TMap.Grow(APlayer: TPlayer);
var
  I: Integer;
  Addition: Integer;
  Dies: Integer;
begin
  for I := 0 to Cells.Count - 1 do
  with TCell(Cells[I]) do begin
    if (Player = APlayer) and ((Game.GrowCells = gcPlayerAll) or
    ((Game.GrowCells = gcPlayerCities) and (Terrain = ttCity))) then begin
      if Power < MaxPower then begin
        // Increase units count
        if Game.GrowAmount = gaByOne then Addition := 1
          else if Game.GrowAmount = gaBySquareRoot then begin
            Addition := Trunc(Sqrt(Power));
            if Addition = 0 then Addition := 1;
          end;
        Power := Min(Power + Addition, MaxPower);
      end else
      if Power > MaxPower then begin
        // Reduce units count
        // If cell has more then MaxPower units then additional units dies
        // in twice of squeare root of unites over MaxPower
        Dies := 2 * Trunc(Sqrt(Power - MaxPower));
        Power := Max(Power - Dies, 0);
      end;
    end;
  end;
end;

procedure TMap.PaintCell(Canvas: TCanvas; Pos: TPoint; Text: string; View: TView;
  Cell: TCell);
var
  I: Integer;
  TextPos: TPoint;
  Points: array of Classes.TPoint;
  TextSize: TSize;
begin
  with Canvas do begin
    if Assigned(View.FocusedCell) and (View.FocusedCell = Cell) then begin
      Pen.Color := clYellow;
      Pen.Style := psSolid;
      Pen.Width := 1;
    end else
    if Cell.Terrain = ttCity then begin
      // Cannot set clear border as it will display shifted on gtk2
      //Pen.Style := psClear;
      Pen.Color := clBlack;
      Pen.Style := psSolid;
      Pen.Width := 3;
    end else begin
      // Cannot set clear border as it will display shifted on gtk2
      //Pen.Style := psClear;
      Pen.Color := Brush.Color;
      Pen.Style := psSolid;
      Pen.Width := 0;
    end;
    // Transform view
    SetLength(Points, Length(Cell.Polygon.Points));
    for I := 0 to Length(Points) - 1 do
      Points[I] := PointToStdPoint(View.CellToCanvasPos(Cell.Polygon.Points[I]));
    Brush.Style := bsSolid;
    //Polygon(Points, False, 0, Length(Points));
    TCanvasEx.PolygonEx(Canvas, Points, False);
    //MoveTo(Points[0].X, Points[0].Y);
    //LineTo(Points[1].X, Points[1].Y);

    // Show cell text
    if Text <> '0' then begin
      Pen.Style := psSolid;
      Font.Color := clWhite;
      Brush.Style := bsClear;
      Font.Size := Trunc(42 * View.Zoom);
      TextPos := View.CellToCanvasPos(Pos);
      TextSize := TextExtent(Text);
      TCanvasEx.TextOutEx(Canvas, Round(TextPos.X) - TextSize.cx div 2,
        Round(TextPos.Y) - TextSize.cy div 2, Text, False);
    end;
  end;
end;

procedure TMap.ComputePlayerStats;
var
  Cell: TCell;
begin
  for Cell in Cells do
  with Cell do begin
    if Assigned(Player) then begin
      Player.TotalCells := Player.TotalCells + 1;
      Player.TotalUnits := Player.TotalUnits + Power;
      if Terrain = ttCity then
        Player.TotalCities := Player.TotalCities + 1;
    end;
  end;
end;

procedure TMap.Generate;
var
  X, Y: Integer;
  NewCell: TCell;
begin
  Clear;

  // Allocate and init new
  Cells.Count := FSize.Y * FSize.X;
  FNewCellId := 1;
  for Y := 0 to FSize.Y - 1 do
  for X := 0 to FSize.X - 1 do begin
    NewCell := TCell.Create;
    NewCell.Map := Self;
    NewCell.PosPx := TPoint.Create(X * DefaultCellSize.X, Y * DefaultCellSize.Y);
    NewCell.Id := GetNewCellId;
    SetLength(NewCell.Polygon.Points, 1);
    NewCell.Polygon.Points[0] := NewCell.PosPx;
    Cells[Y * FSize.X + X] := NewCell;
  end;
  FPixelRect := CalculatePixelRect;
end;

procedure TMap.MakeSymetric;
var
  C: Integer;
  I: Integer;
  CellLink: TCellLink;
  OtherCell1: TCell;
  OtherCell2: TCell;
  OppositeCell: TCell;
begin
  // Generic way to create two sides symetric map independent to shape
  for C := 0 to (Cells.Count div 2) - 1 do begin
    TCell(Cells[C]).Terrain := TCell(Cells[Cells.Count - 1 - C]).Terrain;
    TCell(Cells[C]).Power := TCell(Cells[Cells.Count - 1 - C]).Power;

    for I := TCell(Cells[C]).Links.Count - 1 downto 0 do begin
      CellLink := TCell(Cells[C]).Links[I];

      // Remove cells on first half of the map
      if (Cells.IndexOf(CellLink.Cells[0]) <= (Cells.Count div 2)) and
      (Cells.IndexOf(CellLink.Cells[1]) <= (Cells.Count div 2)) then
      begin
        CellLinks.Remove(CellLink);
        Continue;
      end;

      // Make cross half links symetric
      if (Cells.IndexOf(CellLink.Cells[0]) <= (Cells.Count div 2)) and
      (Cells.IndexOf(CellLink.Cells[1]) >= (Cells.Count div 2)) then begin
        OtherCell1 := Cells[Cells.Count - 1 - Cells.IndexOf(CellLink.Cells[1])];
        OtherCell2 := CellLink.Cells[1];
        CellLinks.Remove(CellLink);
        if not Assigned(CellLinks.FindByCells(OtherCell1, OtherCell2)) then
          CellLinks.AddLink(OtherCell1, OtherCell2);
      end else
      if (Cells.IndexOf(CellLink.Cells[0]) >= (Cells.Count div 2)) and
      (Cells.IndexOf(CellLink.Cells[1]) <= (Cells.Count div 2)) then begin
        OtherCell1 := Cells[Cells.Count - 1 - Cells.IndexOf(CellLink.Cells[0])];
        OtherCell2 := CellLink.Cells[0];
        CellLinks.Remove(CellLink);
        if not Assigned(CellLinks.FindByCells(OtherCell1, OtherCell2)) then
          CellLinks.AddLink(OtherCell1, OtherCell2);
      end;
    end;
  end;

  for C := 0 to (Cells.Count div 2) - 1 do begin
    // Make copy of links from second half
    OppositeCell := TCell(Cells[Cells.Count - 1 - C]);
    for CellLink in OppositeCell.Links do
      if (Cells.IndexOf(CellLink.Cells[0]) > (Cells.Count div 2)) and
      (Cells.IndexOf(CellLink.Cells[1]) > (Cells.Count div 2)) then begin
      OtherCell1 := Cells[Cells.Count - 1 - Cells.IndexOf(CellLink.Cells[0])];
      OtherCell2 := Cells[Cells.Count - 1 - Cells.IndexOf(CellLink.Cells[1])];
      if not Assigned(CellLinks.FindByCells(OtherCell1, OtherCell2)) then
        CellLinks.AddLink(OtherCell1, OtherCell2);
    end;
  end;
end;

procedure TMap.CreateLinks;
var
  LastAreaCount: Integer;
begin
  BuildMapAreas;
  LastAreaCount := -1;
  while (Areas.Count > 1) and (Areas.Count <> LastAreaCount) do begin
    LastAreaCount := Areas.Count;
    BuildBridges;
    BuildMapAreas;
  end;
end;

procedure TMap.Clear;
begin
  CellLinks.Clear;
  Cells.Clear;
  FNewCellId := 1;
end;

constructor TMap.Create;
begin
  MaxPower := 99;
  DefaultCellSize := TPoint.Create(220, 220);
  Cells := TCells.Create;
  Cells.Map := Self;
  Size := TPoint.Create(0, 0);
  Image := TImage.Create(nil);
  CellLinks := TCellLinks.Create;
  CellLinks.Map := Self;
  Areas := TMapAreas.Create;
end;

destructor TMap.Destroy;
begin
  Size := TPoint.Create(0, 0);
  FreeAndNil(Areas);
  FreeAndNil(CellLinks);
  FreeAndNil(Image);
  FreeAndNil(Cells);
  inherited Destroy;
end;

function TMap.CalculatePixelRect: TRect;
var
  I: Integer;
  CellRect: TRect;
begin
  Result := TRect.Create(TPoint.Create(0, 0), TPoint.Create(0, 0));
  // This is generic algorithm to determine pixel size of entire map
  for I := 0 to Cells.Count - 1 do begin
    CellRect := TCell(Cells[I]).Polygon.GetRect;
    if I = 0 then Result := CellRect
      else begin
        Result.P1 := TPoint.Min(Result.P1, CellRect.P1);
        Result.P2 := TPoint.Max(Result.P2, CellRect.P2);
      end;
  end;
end;

procedure TMap.ForEachCells(Method: TMethod);
begin

end;

{ TUnitMove }

procedure TUnitMove.SetCellFrom(AValue: TCell);
begin
  if FCellFrom = AValue then Exit;
  if Assigned(AValue) and not Assigned(FCellFrom) then begin
    AValue.MovesFrom.Add(Self);
  end else
  if not Assigned(AValue) and Assigned(FCellFrom) then begin
    FCellFrom.MovesFrom.Remove(Self);
  end;
  FCellFrom := AValue;
end;

procedure TUnitMove.SetCellTo(AValue: TCell);
begin
  if FCellTo = AValue then Exit;
  if Assigned(AValue) and not Assigned(FCellTo) then begin
    AValue.MovesTo.Add(Self);
  end else
  if not Assigned(AValue) and Assigned(FCellTo) then begin
    FCellTo.MovesTo.Remove(Self);
  end;
  FCellTo := AValue;
end;

procedure TUnitMove.LoadFromNode(Node: TDOMNode);
begin
  CountOnce := ReadInteger(Node, 'CountOnce', 0);
  CountRepeat := ReadInteger(Node, 'CountRepeat', 0);
  CellFrom := List.Game.Map.Cells.FindById(ReadInteger(Node, 'CellFrom', 0));
  CellTo := List.Game.Map.Cells.FindById(ReadInteger(Node, 'CellTo', 0));
end;

procedure TUnitMove.SaveToNode(Node: TDOMNode);
begin
  WriteInteger(Node, 'CountOnce', CountOnce);
  WriteInteger(Node, 'CountRepeat', CountRepeat);
  WriteInteger(Node, 'CellFrom', CellFrom.Id);
  WriteInteger(Node, 'CellTo', CellTo.Id);
end;

constructor TUnitMove.Create;
begin
  List := nil; // Is later set to parent list owning item
  FCellFrom := nil;
  FCellTo := nil;
end;

destructor TUnitMove.Destroy;
begin
  CellFrom := nil;
  CellTo := nil;
  List := nil;
  inherited Destroy;
end;

{ TView }

procedure TView.SetZoom(AValue: Double);
begin
  if FZoom = AValue then Exit;
  if AValue = 0 then
    raise Exception.Create(SZeroZoomNotAlowed);
  FZoom := AValue;
  SourceRect := TRect.CreateBounds(TPoint.Create(Trunc(SourceRect.P1.X + SourceRect.Size.X div 2 - DestRect.Size.X / Zoom / 2),
    Trunc(SourceRect.P1.Y +  SourceRect.Size.Y div 2 - DestRect.Size.Y / Zoom / 2)),
    TPoint.Create(Trunc(DestRect.Size.X / Zoom),
    Trunc(DestRect.Size.Y / Zoom)));
end;

procedure TView.Clear;
begin
  FocusedCell := nil;
  SelectedCell := nil;
end;

procedure TView.SetDestRect(AValue: TRect);
var
  Diff: TPoint;
begin
  if FDestRect = AValue then Exit;
  Diff := TPoint.Create(Trunc(DestRect.Size.X / Zoom - AValue.Size.X / Zoom) div 2,
    Trunc(DestRect.Size.Y / Zoom - AValue.Size.Y / Zoom) div 2);
  FDestRect := AValue;
  SourceRect := TRect.CreateBounds(TPoint.Create(SourceRect.P1.X + Diff.X, SourceRect.P1.Y + Diff.Y),
    TPoint.Create(Trunc(DestRect.Size.X / Zoom),
    Trunc(DestRect.Size.Y / Zoom)));
end;

constructor TView.Create;
begin
  Zoom := 1.5;
  Clear;
end;

destructor TView.Destroy;
begin
  inherited Destroy;
end;

{ TCell }

procedure TCell.SetPower(AValue: Integer);
begin
  if FPower = AValue then Exit;
  if AValue < 0 then
    raise Exception.Create(SNegativeCellPowerNotAllowed);
  FPower := AValue;
  //Check;
end;

procedure TCell.ConnectTo(Cell: TCell);
begin
  Cell.Neighbors.Add(Self);
  Neighbors.Add(Cell);
end;

procedure TCell.DisconnectFrom(Cell: TCell);
var
  I: Integer;
begin
  I := Cell.Neighbors.IndexOf(Self);
  if I >= 0 then Cell.Neighbors.Delete(I) else
    raise Exception.Create('Can''t disconnect neigboring cells.');
  I := Neighbors.IndexOf(Cell);
  if I >= 0 then Neighbors.Delete(I)
    else Exception.Create('Can''t disconnect neigboring cells.');
end;

procedure TCell.Check;
var
  AvailPower: Integer;
begin
  AvailPower := GetAvialPower;
  if AvailPower < 0 then
    raise Exception.Create(Format(SUnitPowerMismatch, [Power, -(AvailPower - Power)]));
end;

function TCell.NeighboringToVoid: Boolean;
var
  NeighVoidCount: Integer;
  NeighborCell: TCell;
begin
  NeighVoidCount := 0;
  for NeighborCell in Neighbors do
    if (NeighborCell.Terrain = ttVoid) then Inc(NeighVoidCount);
  Result := NeighVoidCount > 0;
end;

procedure TCell.SetArea(AValue: TMapArea);
begin
  if FArea = AValue then Exit;
  if Assigned(FArea) then FArea.Cells.Remove(Self);
  FArea := AValue;
  if Assigned(FArea) then FArea.Cells.Add(Self);
end;

procedure TCell.AreaExtend;
var
  NeighborCell: TCell;
begin
  for NeighborCell in Neighbors do
  if (NeighborCell.Terrain <> ttVoid) and (not Assigned(NeighborCell.Area)) then begin
    NeighborCell.Area := Area;
    NeighborCell.AreaExtend;
  end;
end;

procedure TCell.FixRefId;
var
  I: Integer;
begin
  Player := Map.Game.Players.FindById(PlayerId);

  Neighbors.Count := Length(NeighborsId);
  for I := 0 to Length(NeighborsId) - 1 do begin
    Neighbors[I] := Map.Cells.FindById(NeighborsId[I]);
  end;
end;

procedure TCell.LoadFromNode(Node: TDOMNode);
var
  Node2: TDOMNode;
  Node3: TDOMNode;
begin
  Id := ReadInteger(Node, 'Id', 0);
  Power := ReadInteger(Node, 'Power', 0);
  Terrain := TTerrainType(ReadInteger(Node, 'Terrain', Integer(ttVoid)));
  PosPx.X := ReadInteger(Node, 'PosX', 0);
  PosPx.Y := ReadInteger(Node, 'PosY', 0);
  PlayerId := ReadInteger(Node, 'Player', 0);

  Node3 := Node.FindNode('Neighbours');
  if Assigned(Node3) then begin
    SetLength(NeighborsId, 0);
    Node2 := Node3.FirstChild;
    while Assigned(Node2) and (Node2.NodeName = 'Neighbour') do begin
      SetLength(NeighborsId, Length(NeighborsId) + 1);
      NeighborsId[High(NeighborsId)] := ReadInteger(Node2, 'Id', 0);
      Node2 := Node2.NextSibling;
    end;
  end;

  Node3 := Node.FindNode('Polygon');
  if Assigned(Node3) then begin
    Polygon.Clear;
    Node2 := Node3.FirstChild;
    while Assigned(Node2) and (Node2.NodeName = 'Point') do begin
      Polygon.AddPoint(TPoint.Create(ReadInteger(Node2, 'X', 0), ReadInteger(Node2, 'Y', 0)));
      Node2 := Node2.NextSibling;
    end;
  end;
end;

procedure TCell.SaveToNode(Node: TDOMNode);
var
  NewNode: TDOMNode;
  NewNode2: TDOMNode;
  I: Integer;
begin
  WriteInteger(Node, 'Id', Id);
  WriteInteger(Node, 'Power', Power);
  WriteInteger(Node, 'Terrain', Integer(Terrain));
  WriteInteger(Node, 'PosX', PosPx.X);
  WriteInteger(Node, 'PosY', PosPx.Y);
  if Assigned(Player) then
    WriteInteger(Node, 'Player', Player.Id)
    else WriteInteger(Node, 'Player', 0);
  NewNode := Node.OwnerDocument.CreateElement('Neighbours');
  Node.AppendChild(NewNode);
  for I := 0 to Neighbors.Count - 1 do begin
    NewNode2 := NewNode.OwnerDocument.CreateElement('Neighbour');
    NewNode.AppendChild(NewNode2);
    WriteInteger(NewNode2, 'Id', TCell(Neighbors[I]).Id);
  end;
  NewNode := Node.OwnerDocument.CreateElement('Polygon');
  Node.AppendChild(NewNode);
  for I := 0 to Length(Polygon.Points) - 1 do begin
    NewNode2 := NewNode.OwnerDocument.CreateElement('Point');
    NewNode.AppendChild(NewNode2);
    WriteInteger(NewNode2, 'X', Polygon.Points[I].X);
    WriteInteger(NewNode2, 'Y', Polygon.Points[I].Y);
  end;
end;

procedure TCell.Assign(Source: TCell);
begin
  Id := Source.Id;
  PosPx := Source.PosPx;
  Terrain := Source.Terrain;
  Polygon := Source.Polygon;
  Player := Source.Player;
  Mark := Source.Mark;
  // TODO: How to copy neighbours and moves list
end;

function TCell.IsVisible(View: TView): Boolean;
var
  RectPolygon, RectView: TRect;
begin
  RectPolygon := Polygon.GetRect;
  RectView := View.SourceRect;
  Result := (
    (RectPolygon.P1.X < RectView.P2.X) and
    (RectPolygon.P2.X > RectView.P1.X) and
    (RectPolygon.P1.Y < RectView.P2.Y) and
    (RectPolygon.P2.Y > RectView.P1.Y)
  );
end;

function TCell.GetColor: TColor;
begin
  if Assigned(Player) then Result := Player.Color
    else Result := clGray;
end;

function TCell.GetAvialPower: Integer;
var
  UnitMove: TUnitMove;
begin
  Result := Power;
  for UnitMove in MovesFrom do
    Result := Result - UnitMove.CountOnce;
end;

function TCell.GetAttackPower: Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := 0 to MovesTo.Count - 1 do
    Result := Result + TUnitMove(MovesTo[I]).CountOnce;
end;

function TCell.ToString: ansistring;
begin
  Result := IntToStr(Id);
end;

constructor TCell.Create;
begin
  Player := nil;
  Neighbors := TCells.Create;
  Neighbors.FreeObjects := False;
  MovesFrom := TUnitMoves.Create;
  MovesFrom.FreeObjects := False;
  MovesTo := TUnitMoves.Create;
  MovesTo.FreeObjects := False;
  Links := TCellLinks.Create;
  Links.FreeObjects := False;
end;

destructor TCell.Destroy;
var
  I: Integer;
begin
  for I := MovesFrom.Count - 1 downto 0 do
    TUnitMove(MovesFrom[I]).List.Remove(TUnitMove(MovesFrom[I]));
  FreeAndNil(MovesFrom);
  for I := MovesTo.Count - 1 downto 0 do
    TUnitMove(MovesTo[I]).List.Remove(TUnitMove(MovesTo[I]));
  FreeAndNil(MovesTo);
  for I := Links.Count - 1 downto 0 do
    FMap.CellLinks.Remove(Links[I]);
  FreeAndNil(Links);
  for I := Neighbors.Count - 1 downto 0 do
    if TCell(Neighbors[I]).Neighbors.Remove(Self) = -1 then
      raise Exception.Create(SCellRemoveNeighborError);
  FreeAndNil(Neighbors);
  inherited Destroy;
end;

{ TPlayer }

function TView.CanvasToCellPos(Pos: TPoint): TPoint;
begin
  Result := TPoint.Create(Trunc(Pos.X / Zoom + SourceRect.P1.X),
    Trunc(Pos.Y / Zoom + SourceRect.P1.Y));
end;

function TView.CellToCanvasPos(Pos: TPoint): TPoint;
begin
  Result := TPoint.Create(Trunc((Pos.X - SourceRect.P1.X) * Zoom),
    Trunc((Pos.Y - SourceRect.P1.Y) * Zoom));
end;

function TView.CanvasToCellRect(Pos: TRect): TRect;
begin
  Result.P1 := CanvasToCellPos(Pos.P1);
  Result.P2 := CanvasToCellPos(Pos.P2);
end;

function TView.CellToCanvasRect(Pos: TRect): TRect;
begin
  Result.P1 := CellToCanvasPos(Pos.P1);
  Result.P2 := CellToCanvasPos(Pos.P2);
end;

procedure TView.Assign(Source: TView);
begin
  SourceRect := Source.SourceRect;
  FDestRect := Source.DestRect;
  FZoom := Source.Zoom;
  SelectedCell := Source.SelectedCell;
  FocusedCell := Source.FocusedCell;
end;

procedure TPlayer.SetGame(AValue: TGame);
begin
  if FGame = AValue then Exit;
  FGame := AValue;
  Moves.Game := AValue;
  Computer.Game := AValue;
end;

procedure TPlayer.Clear;
begin
  TurnStats.Clear;
  Moves.Clear;
end;

procedure TPlayer.SetClient(AValue: TClient);
begin
  if FClient=AValue then Exit;
  if Assigned(FClient) then FClient.FControlPlayer := nil;
  FClient := AValue;
  if Assigned(FClient) then FClient.FControlPlayer := Self;
end;

procedure TPlayer.LoadFromNode(Node: TDOMNode);
var
  NewNode: TDOMNode;
begin
  Id := ReadInteger(Node, 'Id', 0);
  Name := ReadString(Node, 'Name', '');
  Color := ReadInteger(Node, 'Color', clSilver);
  Mode := TPlayerMode(ReadInteger(Node, 'Mode', Integer(pmHuman)));
  StartCell := FGame.Map.Cells.FindById(ReadInteger(Node, 'StartCell', 0));
  StartUnits := ReadInteger(Node, 'StartUnits', 0);
  Agressivity := TComputerAgressivity(ReadInteger(Node, 'Agressivity', Integer(caMedium)));
  Defensive := ReadBoolean(Node, 'Defensive', False);

  with Node do begin
    NewNode := FindNode('Map');
    if Assigned(NewNode) then
      PlayerMap.LoadFromNode(NewNode);
  end;
  with Node do begin
    NewNode := FindNode('UnitMoves');
    if Assigned(NewNode) then
      Moves.LoadFromNode(NewNode);
  end;
  with Node do begin
    NewNode := FindNode('TurnStats');
    if Assigned(NewNode) then
      TurnStats.LoadFromNode(NewNode);
  end;
end;

procedure TPlayer.SaveToNode(Node: TDOMNode);
var
  NewNode: TDOMNode;
begin
  WriteInteger(Node, 'Id', Id);
  WriteString(Node, 'Name', Name);
  WriteInteger(Node, 'Color', Color);
  WriteInteger(Node, 'Mode', Integer(Mode));
  WriteInteger(Node, 'StartCell', StartCell.Id);
  WriteInteger(Node, 'StartUnits', StartUnits);
  WriteInteger(Node, 'Agressivity', Integer(Agressivity));
  WriteBoolean(Node, 'Defensive', Defensive);

  with Node do begin
    NewNode := OwnerDocument.CreateElement('Map');
    AppendChild(NewNode);
    PlayerMap.SaveToNode(NewNode);
  end;
  with Node do begin
    NewNode := OwnerDocument.CreateElement('UnitMoves');
    AppendChild(NewNode);
    Moves.SaveToNode(NewNode);
  end;
  with Node do begin
    NewNode := OwnerDocument.CreateElement('TurnStats');
    AppendChild(NewNode);
    TurnStats.SaveToNode(NewNode);
  end;
end;

function CellCompare(const Item1, Item2: TCell): Integer;
begin
  if Item1.Power > Item2.Power then Result := 1
  else if Item1.Power < Item2.Power then Result := -1
  else Result := 0;
end;

function CellCompareDescending(const Item1, Item2: TCell): Integer;
begin
  if Item1.Power > Item2.Power then Result := -1
  else if Item1.Power < Item2.Power then Result := 1
  else Result := 0;
end;

procedure TComputer.AttackNeutral;
var
  AllCells: TCells;
  TotalPower: Integer;
  AttackPower: Integer;
  TotalAttackPower: Integer;
  CanAttack: Integer;
  TargetCells: TCells;
  Cell: TCell;
  NeighborCell: TCell;
const
  AttackDiff = 1;
begin
  AllCells := Game.Map.Cells;
  TargetCells := TCells.Create;
  TargetCells.FreeObjects := False;

  // Get list of all attack target cells
  for Cell in AllCells do
  with Cell do begin
    if (Terrain <> ttVoid) and (Player = nil) then begin
      CanAttack := 0;
      for NeighborCell in Neighbors do
      if NeighborCell.Player = Game.CurrentPlayer then begin
        Inc(CanAttack);
      end;
      if CanAttack > 0 then TargetCells.Add(Cell);
    end;
  end;

  // Sort ascending to attack cells with lower power first
  // Low power cells are better for expanding our teritorry
  TargetCells.Sort(CellCompare);

  for Cell in TargetCells do
  with Cell do begin
      // Attack to not owned cell yet
      // Count own possible power
      TotalPower := 0;
      for NeighborCell in Neighbors do
      if NeighborCell.Player = Game.CurrentPlayer then
        TotalPower := TotalPower + NeighborCell.GetAvialPower;

      // Attack if target is weaker
      if TotalPower >= (Power + AttackDiff) then begin
        TotalAttackPower := 0;
        for NeighborCell in Neighbors do
        if NeighborCell.Player = Game.CurrentPlayer then begin
          // Use only necessary power
          AttackPower := Power - TotalAttackPower + AttackDiff;
          if NeighborCell.GetAvialPower < AttackPower then
            AttackPower := NeighborCell.GetAvialPower;
          Self.Player.SetMove(NeighborCell, Cell, AttackPower, False);
          TotalAttackPower := TotalAttackPower + AttackPower;
        end;
      end;
  end;

  FreeAndNil(TargetCells);
end;

procedure TComputer.AttackPlayers;
var
  AllCells: TCells;
  TotalPower: Integer;
  AttackPower: Integer;
  TotalAttackPower: Integer;
  CanAttack: Integer;
  TargetCells: TCells;
  TargetCell: TCell;
  NeighborCell: TCell;
begin
  if Game.CurrentPlayer.Defensive then Exit;

  AllCells := Game.Map.Cells;
  TargetCells := TCells.Create;
  TargetCells.FreeObjects := False;

  // Get list of all attack target cells
  for TargetCell in AllCells do begin
    if (TargetCell.Terrain <> ttVoid) and (TargetCell.Player <> Player) and
      (TargetCell.Player <> nil) then begin
      CanAttack := 0;
      for NeighborCell in TargetCell.Neighbors do
      if NeighborCell.Player = Player then begin
        Inc(CanAttack);
      end;
      if CanAttack > 0 then TargetCells.Add(TargetCell);
    end;
  end;

  // Sort descending to attack cells with higher power first
  // Higher power enemy cells can grow faster and is more dangerous
  TargetCells.Sort(CellCompareDescending);

  for TargetCell in TargetCells do begin
      // Attack to not owned cell yet
      // Count own possible power
      TotalPower := 0;
      for NeighborCell in TargetCell.Neighbors do
      if NeighborCell.Player = Player then begin
        TotalPower := TotalPower + NeighborCell.GetAvialPower;
      end;
      // Attack if target is weaker
      if Game.AttackProbability(TotalPower, TargetCell.Power) >=
        ComputerAggroProbability[Player.Agressivity] then begin
        // Try to limit total attacking power to necessary minimum
        while Game.AttackProbability(TotalPower - 1, TargetCell.Power) >=
          ComputerAggroProbability[Player.Agressivity] do
          Dec(TotalPower);

        // Collect required attack units from our cells
        TotalAttackPower := 0;
        for NeighborCell in TargetCell.Neighbors do
        if NeighborCell.Player = Player then begin
          // Use only necessary power
          AttackPower := TotalPower - TotalAttackPower;
          if NeighborCell.GetAvialPower < AttackPower then
            AttackPower := NeighborCell.GetAvialPower;
          Self.Player.SetMove(NeighborCell, TargetCell, AttackPower, False);
          TotalAttackPower := TotalAttackPower + AttackPower;
          if TotalAttackPower >= TotalPower then Break;
        end;
      end;
  end;

  FreeAndNil(TargetCells);
end;

procedure TComputer.InnerMoves;
var
  AllCells: TCells;
  I, J: Integer;
  C: Integer;
  CanAttack: Integer;
  TargetCells: TCells;
  NewTargetCells: TCells;
  Cells2: TCells;
  MovedPower: Integer;
begin
  // We need to move available power to borders to be available for attacks
  // or defense
  AllCells := Game.Map.Cells;
  TargetCells := TCells.Create;
  TargetCells.FreeObjects := False;
  NewTargetCells := TCells.Create;
  NewTargetCells.FreeObjects := False;

  // Get list of all enemy border cells
  for C := 0 to AllCells.Count - 1 do
  with TCell(AllCells[C]) do begin
    if (Player <> Game.CurrentPlayer) and (Player <> nil) and (Terrain <> ttVoid) then begin
      CanAttack := 0;
      for I := 0 to Neighbors.Count - 1 do
      if ((TCell(Neighbors[I]).Player = Game.CurrentPlayer) or
      (TCell(Neighbors[I]).Player = nil)) and (TCell(Neighbors[I]).Terrain <> ttVoid) then begin
        Inc(CanAttack);
      end;
      if CanAttack > 0 then TargetCells.Add(AllCells[C]);
    end;
  end;

  if CellProcessDirection then begin
    // Reverse array
    for I := 0 to (TargetCells.Count div 2) - 1 do
      TargetCells.Exchange(I, TargetCells.Count - 1 - I);
  end;

  Game.Map.Cells.ClearMark;

  while TargetCells.Count > 0 do begin
    // Set mark for selected border cells
    for C := 0 to TargetCells.Count - 1 do
      TCell(TargetCells[C]).Mark := True;

    // Move all power from unmarked cells and mark them
    NewTargetCells.Count := 0;
    for C := 0 to TargetCells.Count - 1 do
    with TCell(TargetCells[C]) do begin
      for I := 0 to Neighbors.Count - 1 do begin
        if (TCell(Neighbors[I]).Terrain <> ttVoid) and (not TCell(Neighbors[I]).Mark) then begin
          if (TCell(TargetCells[C]).Player = Game.CurrentPlayer) and
          (TCell(Neighbors[I]).Player = Game.CurrentPlayer) then begin
            // Do not take units from front line
            Cells2 := TCell(Neighbors[I]).Neighbors;
            CanAttack := 0;
            for J := 0 to Cells2.Count - 1 do
            if ((TCell(Cells2[J]).Player <> Game.CurrentPlayer) or (TCell(Cells2[J]).Player = nil))
            and (TCell(Cells2[J]).Terrain <> ttVoid) then begin
              Inc(CanAttack);
            end;
            if CanAttack = 0 then begin
              MovedPower := TCell(Neighbors[I]).GetAvialPower;
              if (TCell(TargetCells[C]).GetAvialPower + TCell(TargetCells[C]).GetAttackPower + MovedPower) > Game.Map.MaxPower then
                MovedPower := Game.Map.MaxPower - TCell(TargetCells[C]).GetAvialPower - TCell(TargetCells[C]).GetAttackPower;
              Player.SetMove(TCell(Neighbors[I]), TCell(TargetCells[C]), MovedPower, False);
            end;
          end;
          TCell(Neighbors[I]).Mark := True;
          NewTargetCells.Add(TCell(Neighbors[I]));
        end;
      end;
    end;

    // Use source cells NewTargetCells as new TargetCells
    FreeAndNil(TargetCells);
    TargetCells := NewTargetCells;
    NewTargetCells := TCells.Create;
    NewTargetCells.FreeObjects := False;
  end;

  FreeAndNil(TargetCells);
  FreeAndNil(NewTargetCells);
end;

procedure TComputer.IncreaseMoves;
var
  Move: TUnitMove;
  AvailPower: Integer;
begin
  // If available power remains then use all for existed unit moves
  for Move in Player.Moves do
  with Move do begin
    if CellFrom.GetAvialPower > 0 then begin
      AvailPower := CellFrom.GetAvialPower;
      CountOnce := CountOnce + Min(AvailPower div CellFrom.MovesFrom.Count, AvailPower);
    end;
  end;
end;

procedure TComputer.Process;
begin
  AttackPlayers;
  AttackNeutral;
  InnerMoves;
  IncreaseMoves;
  //FallBack;
  CellProcessDirection := not CellProcessDirection;
end;

procedure TComputer.FallBack;
var
  C: Integer;
  I: Integer;
  AllCells: TCells;
  BorderCells: TCells;
  EnemyPower: Integer;
begin
  BorderCells := TCells.Create;
  BorderCells.FreeObjects := False;
  AllCells := Game.Map.Cells;

  // Get list of border cells
  for C := 0 to AllCells.Count - 1 do
  with TCell(AllCells[C]) do begin
    if (Terrain <> ttVoid) and (Player = Game.CurrentPlayer)  then begin
      if AttackersCount(TCell(AllCells[C])) > 0 then
        BorderCells.Add(AllCells[C]);
    end;
  end;

  // Move all units back to inner area from weak border cells
  for C := 0 to BorderCells.Count - 1 do
  with TCell(BorderCells[C]) do begin
    // Calculate enemy power
    // TODO: Do not sum different enemy power to one value
    EnemyPower := 0;
    for I := 0 to Neighbors.Count - 1 do
    if (TCell(Neighbors[I]).Player <> Game.CurrentPlayer) and (TCell(Neighbors[I]).Player <> nil) then begin
      Inc(EnemyPower, TCell(Neighbors[I]).Power);
    end;
    if EnemyPower > (GetAvialPower + GetAttackPower) then begin
      // Fallback
      for I := MovesTo.Count - 1 downto 0 do
        Player.Moves.Remove(MovesTo[I]);
      for I := 0 to Neighbors.Count - 1 do
      if (TCell(Neighbors[I]).Player = Player) and (AttackersCount(TCell(Neighbors[I])) = 0) then begin
        Player.SetMove(TCell(BorderCells[C]), TCell(Neighbors[I]), GetAvialPower, False);
        Break;
      end;
    end;
  end;

  FreeAndNil(BorderCells);
end;

function TComputer.AttackersCount(Cell: TCell): Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := 0 to Cell.Neighbors.Count - 1 do
  if (TCell(Cell.Neighbors[I]).Player <> Game.CurrentPlayer) and
    (TCell(Cell.Neighbors[I]).Player <> nil) then begin
      Inc(Result);
  end;
end;

procedure TView.SelectCell(Pos: TPoint; Player: TPlayer; ShiftState: TShiftState);
var
  NewSelectedCell: TCell;
  UnitMove: TUnitMove;
  I: Integer;
begin
  NewSelectedCell := Game.Map.PosToCell(CanvasToCellPos(Pos), Self);
  if Assigned(NewSelectedCell) then begin
    if Assigned(SelectedCell) and Game.Map.IsCellsNeighbor(NewSelectedCell, SelectedCell) then begin
      if ssShift in ShiftState then begin
        // Make maximum unit move without confirmation dialog
        for I := SelectedCell.MovesFrom.Count - 1 downto 0 do begin
          Player.Moves.Remove(SelectedCell.MovesFrom[I]);
        end;
        Game.CurrentPlayer.SetMove(SelectedCell, NewSelectedCell, SelectedCell.Power, False);
        SelectedCell := nil;
      end else
      if ssCtrl in ShiftState then begin
        // If CTRL key pressed then storno all moved from selected cell and
        // move all power to new selected cell
        for I := SelectedCell.MovesFrom.Count - 1 downto 0 do
          Player.Moves.Remove(SelectedCell.MovesFrom[I]);
        UnitMove := Game.CurrentPlayer.SetMove(SelectedCell, NewSelectedCell, SelectedCell.Power, False);
        if Assigned(UnitMove) then
          UnitMove.CountRepeat := Player.Game.Map.MaxPower;
        if NewSelectedCell.Player = Player then SelectedCell := NewSelectedCell
          else SelectedCell := nil;
      end else begin
        Game.CurrentPlayer.SetMove(SelectedCell, NewSelectedCell, SelectedCell.Power);
        SelectedCell := nil;
      end;
    end else
    if (NewSelectedCell <> SelectedCell) and (NewSelectedCell.Player = Player) then
      SelectedCell := NewSelectedCell
    else
    if (NewSelectedCell = SelectedCell) and (NewSelectedCell.Player = Player) then
      SelectedCell := nil;
  end;
end;

procedure TView.CenterMap;
var
  MapRect: TRect;
begin
  MapRect := Game.Map.PixelRect;
  SourceRect := TRect.CreateBounds(TPoint.Create(MapRect.P1.X + MapRect.Size.X div 2 - SourceRect.Size.X div 2,
    MapRect.P1.Y + MapRect.Size.Y div 2 - SourceRect.Size.Y div 2),
    TPoint.Create(SourceRect.Size.X,
    SourceRect.Size.Y));
end;

procedure TView.CenterPlayerCity(Player: TPlayer);
begin
  SourceRect := TRect.CreateBounds(TPoint.Create(Player.StartCell.PosPx.X - SourceRect.Size.X div 2,
    Player.StartCell.PosPx.Y - SourceRect.Size.Y div 2),
    TPoint.Create(SourceRect.Size.X,
    SourceRect.Size.Y));
end;

procedure TPlayer.Paint(Canvas: TCanvas; View: TView);
begin
  PlayerMap.Paint(Canvas, View);
end;

constructor TPlayer.Create;
begin
  Moves := TUnitMoves.Create;
  StartUnits := DefaultPlayerStartUnits;
  StartCell := nil;
  PlayerMap := TPlayerMap.Create;
  PlayerMap.Player := Self;
  TurnStats := TGameTurnStats.Create;
  Computer := TComputer.Create;
  Computer.Player := Self;
end;

destructor TPlayer.Destroy;
begin
  Client := nil;
  FreeAndNil(Computer);
  FreeAndNil(TurnStats);
  FreeAndNil(PlayerMap);
  FreeAndNil(Moves);
  inherited Destroy;
end;

procedure TPlayer.Assign(Source: TPlayer);
begin
  Id := Source.Id;
  Name := Source.Name;
  Color := Source.Color;
  Mode := Source.Mode;
  Game := Source.Game;
  TotalCells := Source.TotalCells;
  TotalUnits := Source.TotalUnits;
  StartUnits := Source.StartUnits;
  StartCell := Source.StartCell;
  Agressivity := Source.Agressivity;
  Defensive := Source.Defensive;
  Computer.Game := Source.Computer.Game;
  Computer.CellProcessDirection := Source.Computer.CellProcessDirection;
end;

procedure TPlayer.LoadConfig(Config: TXmlConfig; Path: string);
begin
  with Config do begin
    Self.Name := string(GetValue(DOMString(Path + '/Name'), ''));
    Color := TColor(GetValue(DOMString(Path + '/Color'), 0));
    StartUnits := GetValue(DOMString(Path + '/StartUnits'), 5);
    Mode := TPlayerMode(GetValue(DOMString(Path + '/Mode'), 0));
    Defensive := GetValue(DOMString(Path + '/Defensive'), False);
    Agressivity := TComputerAgressivity(GetValue(DOMString(Path + '/Agressivity'), 0));
  end;
end;

procedure TPlayer.SaveConfig(Config: TXmlConfig; Path: string);
begin
  with Config do begin
    SetValue(DOMString(Path + '/Name'), DOMString(Self.Name));
    SetValue(DOMString(Path + '/Color'), Integer(Color));
    SetValue(DOMString(Path + '/StartUnits'), StartUnits);
    SetValue(DOMString(Path + '/Mode'), Integer(Mode));
    SetValue(DOMString(Path + '/Defensive'), Defensive);
    SetValue(DOMString(Path + '/Agressivity'), Integer(Agressivity));
  end;
end;

{ TGame }

function ComparePointer(const Item1, Item2: Integer): Integer;
begin
  Result := -CompareValue(Item1, Item2);
end;

procedure TPlayer.Attack(var AttackPower, DefendPower: Integer);
var
  AttackerDiceCount: Integer;
  DefenderDiceCount: Integer;
  S: string;
  I: Integer;
  AttackRolls: TFPGList<Integer>;
  DefendRolls: TFPGList<Integer>;
begin
  AttackRolls := TFPGList<Integer>.Create;
  DefendRolls := TFPGList<Integer>.Create;
  if AttackPower < 1 then
    raise Exception.Create(SAttackerPowerPositive);
  if DefendPower < 0 then
    raise Exception.Create(SDefenderPowerPositive);
  while (AttackPower > 0) and (DefendPower > 0) do begin
    // Risk game rules:
    // Each side do their dice roll and compare result. Defender wins tie.
    // Attacker can use three dices and defender two
    AttackerDiceCount := Min(AttackPower, 3);
    DefenderDiceCount := Min(DefendPower, 2);
    // Roll and sort numbers
    AttackRolls.Count := AttackerDiceCount;
    for I := 0 to AttackerDiceCount - 1 do begin
      AttackRolls[I] := Random(7);
    end;
    AttackRolls.Sort(ComparePointer);
    S := 'Att:';
    for I := 0 to AttackerDiceCount - 1 do
      S := S + IntToStr(Integer(AttackRolls[I])) + ', ';
    DefendRolls.Count := DefenderDiceCount;
    for I := 0 to DefenderDiceCount - 1 do begin
      DefendRolls[I] := Random(7);
    end;
    DefendRolls.Sort(ComparePointer);
    S := S + ' Def:';
    for I := 0 to DefenderDiceCount - 1 do
      S := S + IntToStr(Integer(DefendRolls[I])) + ', ';
    // Resolution
    for I := 0 to Min(AttackerDiceCount, DefenderDiceCount) - 1 do
      if AttackRolls[I] > DefendRolls[I] then Dec(DefendPower)
      else Dec(AttackPower);
  end;
  FreeAndNil(AttackRolls);
  FreeAndNil(DefendRolls);
end;

function TGame.AttackProbability(AttackCount, DefendCount: Integer): Double;
var
  OA, OD: Integer;
  Len: Integer;
  I: Integer;
begin
  if AttackCount = 0 then begin
    Result := 0;
    Exit;
  end;
  if DefendCount = 0 then begin
    Result := 1;
    Exit;
  end;

  // Enlarge probability cache table on demand
  if Length(ProbabilityMatrix) < AttackCount then begin
    SetLength(ProbabilityMatrix, AttackCount);
  end;
  if Length(ProbabilityMatrix[AttackCount - 1]) < DefendCount then begin
    Len := Length(ProbabilityMatrix[AttackCount - 1]);
    SetLength(ProbabilityMatrix[AttackCount - 1], DefendCount);
    for I := Len to Length(ProbabilityMatrix[AttackCount - 1]) - 1 do
      ProbabilityMatrix[AttackCount - 1][I] := -1;
  end;

  if ProbabilityMatrix[AttackCount - 1, DefendCount - 1] <> -1 then begin
    // Use cached value
    Result := ProbabilityMatrix[AttackCount - 1, DefendCount - 1];
    Exit;
  end else Result := 1;

  OA := Min(AttackCount, 3);
  OD := Min(DefendCount, 2);

  if (OA = 1) and (OD = 1) then
    Result := 0.4167 * AttackProbability(AttackCount, DefendCount - 1) +
      0.5833 * AttackProbability(AttackCount - 1, DefendCount)
  else if (OA = 2) and (OD = 1) then
    Result := 0.5787 * AttackProbability(AttackCount, DefendCount - 1) +
      0.4213 * AttackProbability(AttackCount - 1, DefendCount)
  else if (OA = 3) and (OD = 1) then
    Result := 0.6597 * AttackProbability(AttackCount, DefendCount - 1) +
      0.3403 * AttackProbability(AttackCount - 1, DefendCount)
  else if (OA = 1) and (OD = 2) then
    Result := 0.2546 * AttackProbability(AttackCount, DefendCount - 1) +
      0.7454 * AttackProbability(AttackCount - 1, DefendCount)
  else if (OA = 2) and (OD = 2) then
    Result := 0.2276 * AttackProbability(AttackCount, DefendCount - 2) +
      0.4483 * AttackProbability(AttackCount - 2, DefendCount) +
      0.3241 * AttackProbability(AttackCount - 1, DefendCount - 1)
  else if (OA = 3) and (OD = 2) then
    Result := 0.3717 * AttackProbability(AttackCount, DefendCount - 2) +
      0.2926 * AttackProbability(AttackCount - 2, DefendCount) +
      0.3358 * AttackProbability(AttackCount - 1, DefendCount - 1);
  ProbabilityMatrix[AttackCount - 1, DefendCount - 1] := Result;
end;

procedure TPlayer.MoveAll;
var
  AttackerPower: Integer;
  DefenderPower: Integer;
  UnitCount: Integer;
  UnitMove: TUnitMove;
begin
  for UnitMove in Moves do
  with UnitMove do begin
  if CountOnce > 0 then begin
    if CellFrom.Player = Self then begin
      UnitCount := CountOnce;
      if CountOnce > CellFrom.Power then
        UnitCount := CellFrom.Power;
      CountOnce := 0;
      if CellTo.Player = Self then begin
        // Inner move
        CellTo.Power := CellTo.Power + UnitCount;
      end else begin
        AttackerPower := UnitCount;
        DefenderPower := CellTo.Power;
        Attack(AttackerPower, DefenderPower);
        if DefenderPower = 0 then begin
          // Attacker wins with possible loses
          ClearMovesFromCell(CellTo);
          CellTo.Player := Self;
          CellTo.Power := AttackerPower;
        end else
        if AttackerPower = 0 then begin
          // Defender wins with possible loses
          CellTo.Power := DefenderPower;
        end else
          raise Exception.Create(SUnfinishedBattle);
      end;
      CellFrom.Power := CellFrom.Power - UnitCount;
    end;
    end;
  end;

  RemoveEmptyUnitMoves;
end;

procedure TPlayer.ReduceMovesPower;
var
  UnitMove: TUnitMove;
  Power: Integer;
begin
  // Power of cell can be reduced by unsucessful enemy attack
  for UnitMove in Moves do begin
    Power := UnitMove.CellFrom.GetAvialPower;
    if Power < 0 then begin
      if Abs(Power) <= UnitMove.CountOnce then
        UnitMove.CountOnce := UnitMove.CountOnce - Abs(Power)
    end;
  end;
end;

procedure TPlayer.ClearMovesFromCell(Cell: TCell);
var
  I: Integer;
begin
  if Assigned(Cell.Player) then
  for I := Cell.Player.Moves.Count - 1 downto 0 do
  if TUnitMove(Cell.Player.Moves[I]).CellFrom = Cell then
    Cell.Player.Moves.Delete(I);
end;

procedure TGame.SetMapType(AValue: TMapType);
var
  OldMap: TMap;
begin
  if FMapType = AValue then Exit;
  OldMap := Map;
  case AValue of
    mtNone: Map := TMap.Create;
    mtHexagon: Map := THexMap.Create;
    mtSquare: Map := TSquareMap.Create;
    mtTriangle: Map := TTriangleMap.Create;
    mtVoronoi: Map := TVoronoiMap.Create;
    else Map := TMap.Create;
  end;
  Map.Assign(OldMap);
  FreeAndNil(OldMap);
  FMapType := AValue;
end;

function TPlayer.SetMove(CellFrom, CellTo: TCell; Power: Integer; Confirmation: Boolean = True): TUnitMove;
var
  NewMove: TUnitMove;
  CountOnce: Integer;
  CountRepeat: Integer;
  Confirm: Boolean;
begin
  Confirm := True;
  Result := Moves.SearchByFromTo(CellFrom, CellTo);

  if Assigned(Result) then begin
    CountOnce := Result.CountOnce;
    CountRepeat := Result.CountRepeat;
    if (Mode = pmHuman) and Confirmation and
      Assigned(Game.FOnMove) then Game.FOnMove(CellFrom, CellTo, CountOnce, CountRepeat, True, Confirm);
  end else begin
    CountOnce := Power;
    CountRepeat := 0;
    if (Mode = pmHuman) and Confirmation and
      Assigned(Game.FOnMove) then Game.FOnMove(CellFrom, CellTo, CountOnce, CountRepeat, False, Confirm);
  end;
  if Confirm then begin
    if Assigned(Result) then begin
      // Already have such move
      if (CountOnce = 0) and (CountRepeat = 0) then begin
          Result.List.Remove(Result);
        end else begin
          Result.CountOnce := CountOnce;
          Result.CountRepeat := CountRepeat;
          CheckCounterMove(Result);
        end;
    end else begin
      // Add new move
      if (CountOnce > 0) or (CountRepeat > 0) then begin
        NewMove := TUnitMove.Create;
        NewMove.List := Moves;
        NewMove.CellFrom := CellFrom;
        NewMove.CellTo := CellTo;
        NewMove.CountOnce := CountOnce;
        NewMove.CountRepeat := CountRepeat;
        Moves.Add(NewMove);
        Result := NewMove;
        CheckCounterMove(NewMove);
      end;
    end;
    CellFrom.Check;
    if Assigned(Game.FOnMoveUpdated) then Game.FOnMoveUpdated(Result);
  end;
end;

procedure TGame.SetRunning(AValue: Boolean);
var
  I: Integer;
begin
  if FRunning = AValue then Exit;
  if AValue then begin
    if Players.Count < 2 then raise Exception.Create(SMinimumPlayers);
    FRunning := AValue;
  end else begin
    FRunning := AValue;
    for I := 0 to Clients.Count - 1 do
    with TClient(Clients[I]) do begin
      View.Clear;
    end;
  end;
end;

procedure TPlayer.UpdateRepeatMoves;
var
  Move: TUnitMove;
begin
  for Move in Moves do
  with Move do begin
    if CellFrom.Player = Self then
      if CountRepeat <= CellFrom.GetAvialPower then
        CountOnce := CountRepeat
        else CountOnce := CellFrom.GetAvialPower;
  end;
  RemoveEmptyUnitMoves;
end;

procedure TPlayer.RemoveEmptyUnitMoves;
var
  I: Integer;
begin
  // Remove empty moves
  for I := Moves.Count - 1 downto 0 do
  if (TUnitMove(Moves[I]).CellFrom.Player = Self) and
    (TUnitMove(Moves[I]).CountOnce = 0) and (TUnitMove(Moves[I]).CountRepeat = 0) then
    Moves.Delete(I);
end;

function TPlayer.IsAlive: Boolean;
begin
  Result := (TotalCells > 0) and Assigned(StartCell);
end;

procedure TPlayer.CheckCounterMove(Move: TUnitMove);
var
  CounterMove: TUnitMove;
begin
  CounterMove := Moves.SearchByFromTo(Move.CellTo, Move.CellFrom);
  if Assigned(CounterMove) then begin
    // For now, just remove counter move
    Moves.Remove(CounterMove);
  end;
end;

procedure TPlayer.SetMode(AValue: TPlayerMode);
begin
  if FMode = AValue then Exit;
  FMode := AValue;
end;

function TMap.SearchDifferentCellArea(List: TCells; SourceArea, DestArea: TMapArea): TCell;
var
  NewList: TCells;
  NewListVoid: TCells;
  I: Integer;
  C: Integer;
begin
  Result := nil;
  NewList := TCells.Create;
  NewList.FreeObjects := False;
  NewListVoid := TCells.Create;
  NewListVoid.FreeObjects := False;

  for C := 0 to List.Count - 1 do
  with TCell(List[C]) do begin
    for I := 0 to Neighbors.Count - 1 do
    with TCell(Neighbors[I]) do
    if (not Mark) and (Terrain <> ttVoid) and (Area <> SourceArea) and ((DestArea = nil) or (DestArea = Area)) then begin
      NewList.Add(TCell(TCell(List[C]).Neighbors[I]));
      Mark := True;
    end else
    if (not Mark) and (Terrain = ttVoid) then begin
      NewListVoid.Add(TCell(TCell(List[C]).Neighbors[I]));
      Mark := True;
    end;
  end;

  if NewList.Count > 0 then begin
    // We found cell with different area
    Result := TCell(NewList[Random(NewList.Count)]);
  end else
  if NewListVoid.Count > 0 then begin
    // Cell was not found but we have more void cells to check
    Result := SearchDifferentCellArea(NewListVoid, SourceArea, DestArea);
  end;

  FreeAndNil(NewListVoid);
  FreeAndNil(NewList);
end;

procedure TGame.BuildTerrain;
var
  Cell: TCell;
begin
  if (Map.Shape = msImage) and FileExists(MapImageFileName) and
  (LoadedImageFileName <> MapImageFileName) then begin
    LoadedImageFileName := MapImageFileName;
    Map.Image.Picture.LoadFromFile(MapImageFileName);
  end;

  // Randomize map terrain
  for Cell in Map.Cells do
  with Cell do begin
    if (VoidEnabled and (Random < VoidPercentage / 100)) or
    (Map.IsOutsideShape(PosPx)) then Terrain := ttVoid
      else begin
        if CityEnabled and (Random < CityPercentage / 100) then Terrain := ttCity
          else Terrain := ttNormal;
      end;
    Power := Random(MaxNeutralUnits + 1);
    Player := nil;
  end;
end;

procedure TMap.BuildBridges;
var
  List: TCells;
  BorderList: TCells;
  Cell: TCell;
  FoundCell1: TCell;
  FoundCell2: TCell;
  I: Integer;
  J: Integer;
begin
  List := TCells.Create;
  List.FreeObjects := False;

  BorderList := TCells.Create;
  BorderList.FreeObjects := False;

  // Build area bridges
  if Areas.Count > 1 then
  for I := 0 to Areas.Count - 1 do
  with TMapArea(Areas[I]) do begin
    GetBorderCells(BorderList);
    if BorderList.Count > 0 then
    for J := 0 to 4 do begin

    Cell := TCell(BorderList[Random(BorderList.Count)]);
    List.Clear;
    List.Add(Cell);

    Map.Cells.ClearMark;

    // Find nearest cell with different area
    FoundCell1 := SearchDifferentCellArea(List, TMapArea(Map.Areas[I]), nil);
    if Assigned(FoundCell1) then begin
      // Again find back nearest cell with different area.
      // This will ensure that both cells are closest ones

      Map.Cells.ClearMark;
      List[0] := FoundCell1;
      FoundCell2 := SearchDifferentCellArea(List, FoundCell1.Area, TMapArea(Map.Areas[I]));
      if Assigned(FoundCell2) then begin
        // Check if link doesn't exist already
        if not Assigned(FoundCell1.Links.FindByCells(FoundCell1, FoundCell2)) then begin
          Map.CellLinks.AddLink(FoundCell1, FoundCell2);
          Inc(BridgeCount);
        end;
      end;
    end;
    end;
  end;
  FreeAndNil(List);
  FreeAndNil(BorderList);
end;

procedure TMap.BuildMapAreas;
var
  C: Integer;
  NewArea: TMapArea;
begin
  for C := 0 to Cells.Count - 1 do
  with TCell(Cells[C]) do
    Area := nil;
  Areas.Clear;
  for C := 0 to Cells.Count - 1 do
  with TCell(Cells[C]) do
  if (Terrain <> ttVoid) and (not Assigned(Area)) then begin
    NewArea := TMapArea.Create;
    NewArea.Id := Map.Areas.Count;
    NewArea.Map := Map;
    Areas.Add(NewArea);
    Area := NewArea;
    AreaExtend;
  end;
end;

procedure TGame.InitClients;
var
  Client: TClient;
  Player: TPlayer;
begin
  Clients.Clear;
  Clients.New('Spectator');

  for Player in Players do
  with Player do
  if Mode = pmHuman then begin
    Clients.New(Player.Name);
    Player.Client := TClient(Clients.Last);
  end;

  for Client in Clients do
  with Client do begin
    View.Clear;
    View.Zoom := 1;
    if Assigned(ControlPlayer) and Assigned(ControlPlayer.StartCell) then
      View.CenterPlayerCity(ControlPlayer)
      else View.CenterMap;
  end;
end;

procedure TGame.SelectPlayerStartCell(Player: TPlayer);
var
  LongestDistance: Integer;
  Cell: TCell;
  List: TCells;
  I: Integer;
begin
  with Player do begin
    Map.Cells.ClearMark;
    Map.Cells.ClearWeight;
    CalculatePlayersDistance;

    // Calculate longest distance
    LongestDistance := 0;
    for Cell in Map.Cells do
      if (Cell.Terrain <> ttVoid) and (Cell.Weight > LongestDistance) then
        LongestDistance := Cell.Weight;

    List := TCells.Create;
    try
      List.FreeObjects := False;
      Map.Cells.GetCellsWithWeight(List, Round(LongestDistance * 0.6), Round(LongestDistance * 0.8));

      // Remove cells already allocated to different player
      for I := List.Count - 1 downto 0 do
        if Assigned(TCell(List[I]).Player) then
          List.Delete(I);

      if List.Count > 0 then
        StartCell := List[Random(List.Count)];
    finally
      FreeAndNil(List);
    end;
  end;
end;

procedure TGame.CalculatePlayersDistance;
var
  Player: TPlayer;
  List: TCells;
begin
  for Player in Players do
  if Assigned(Player.StartCell) then begin
    Player.StartCell.Weight := 1;
    Player.StartCell.Mark := True;
    List := TCells.Create;
    List.FreeObjects := False;
    List.Add(Player.StartCell);
    PropagatePlayerDistance(List);
    FreeAndNil(List);
  end;
end;

procedure TGame.PropagatePlayerDistance(List: TCells);
var
  NeighborCell: TCell;
  NeighborList: TCells;
  Cell: TCell;
begin
  NeighborList := TCells.Create;
  NeighborList.FreeObjects := False;

  for Cell in List do begin
    for NeighborCell in Cell.Neighbors do begin
      if (NeighborCell.Terrain <> ttVoid) and
        ((not NeighborCell.Mark) or (NeighborCell.Weight > Cell.Weight + 1)) then begin
        NeighborCell.Weight := Cell.Weight + 1;
        NeighborCell.Mark := True;
        NeighborList.Add(NeighborCell);
      end;
    end;
  end;
  if NeighborList.Count > 0 then
    PropagatePlayerDistance(NeighborList);
  FreeAndNil(NeighborList);
end;

procedure TGame.InitDefaultPlayersSetting;
begin
  PlayersSetting.Clear;
  PlayersSetting.New(SPlayer + ' 1', clBlue, pmHuman);
  PlayersSetting.New(SPlayer + ' 2', clRed, pmComputer);
end;

procedure TGame.SaveConfig(Config: TXmlConfig; Path: string);
begin
  with Config do begin
    SetValue(DOMString(Path + '/GridType'), Integer(MapType));
    SetValue(DOMString(Path + '/MapImage'), DOMString(MapImageFileName));
    SetValue(DOMString(Path + '/SymetricMap'), SymetricMap);
    SetValue(DOMString(Path + '/FogOfWar'), FogOfWar);
    SetValue(DOMString(Path + '/VoidEnabled'), VoidEnabled);
    SetValue(DOMString(Path + '/VoidPercentage'), VoidPercentage);
    SetValue(DOMString(Path + '/MapSizeX'), Map.Size.X);
    SetValue(DOMString(Path + '/MapSizeY'), Map.Size.Y);
    SetValue(DOMString(Path + '/MapShape'), Integer(Map.Shape));
    SetValue(DOMString(Path + '/CityEnabled'), CityEnabled);
    SetValue(DOMString(Path + '/CityPercentage'), CityPercentage);
    SetValue(DOMString(Path + '/BridgeEnabled'), BridgeEnabled);
    SetValue(DOMString(Path + '/GrowAmount'), Integer(GrowAmount));
    SetValue(DOMString(Path + '/GrowCells'), Integer(GrowCells));
    SetValue(DOMString(Path + '/WinObjective'), Integer(WinObjective));
    PlayersSetting.SaveConfig(Config, Path + '/Players');
  end;
end;

procedure TGame.LoadConfig(Config: TXmlConfig; Path: string);
var
  Value: Integer;
begin
  with Config do begin
    MapType := TMapType(GetValue(DOMString(Path + '/GridType'), Integer(mtHexagon)));
    Map.Size := TPoint.Create(GetValue(DOMString(Path + '/MapSizeX'), 10),
      GetValue(DOMString(Path + '/MapSizeY'), 10));
    MapImageFileName := string(GetValue(DOMString(Path + '/MapImage'), DOMString(MapImageFileName)));
    SymetricMap := GetValue(DOMString(Path + '/SymetricMap'), False);
    FogOfWar := GetValue(DOMString(Path + '/FogOfWar'), False);
    VoidEnabled := GetValue(DOMString(Path + '/VoidEnabled'), True);
    VoidPercentage := GetValue(DOMString(Path + '/VoidPercentage'), 20);
    Value := GetValue(DOMString(Path + '/MapShape'), 0);
    if (Value >= Integer(Low(TMapShape))) and (Value <= Integer(High(TMapShape))) then
      Map.Shape := TMapShape(Value) else Map.Shape := Low(TMapShape);
    CityEnabled := GetValue(DOMString(Path + '/CityEnabled'), False);
    CityPercentage := GetValue(DOMString(Path + '/CityPercentage'), 10);
    BridgeEnabled := GetValue(DOMString(Path + '/BridgeEnabled'), True);
    Value := GetValue(DOMString(Path + '/GrowAmount'), Integer(gaBySquareRoot));
    if (Value >= Integer(Low(TGrowAmount))) and (Value <= Integer(High(TGrowAmount))) then
      GrowAmount := TGrowAmount(Value) else GrowAmount := Low(TGrowAmount);
    Value := GetValue(DOMString(Path + '/GrowCells'), Integer(gcPlayerAll));
    if (Value >= Integer(Low(TGrowCells))) and (Value <= Integer(High(TGrowCells))) then
      GrowCells := TGrowCells(Value) else GrowCells := Low(TGrowCells);
    Value := GetValue(DOMString(Path + '/WinObjective'), Integer(woDefeatAllOponents));
    if (Value >= Integer(Low(TWinObjective))) and (Value <= Integer(High(TWinObjective))) then
      WinObjective := TWinObjective(Value) else WinObjective := Low(TWinObjective);
    PlayersSetting.LoadConfig(Config, Path + '/Players');
  end;
end;

procedure TGame.LoadFromFile(FileName: string);
var
  NewNode: TDOMNode;
  Doc: TXMLDocument;
  RootNode: TDOMNode;
  I: Integer;
begin
  Self.FileName := FileName;
  Clear;
  ReadXMLFile(Doc, FileName);
  with Doc do try
    if Doc.DocumentElement.NodeName <> 'XtacticsGame' then
      raise Exception.Create(SWrongFileFormat);
    RootNode := Doc.DocumentElement;
    with RootNode do begin
      SymetricMap := ReadBoolean(RootNode, 'SymetricMap', False);
      FogOfWar := ReadBoolean(RootNode, 'FogOfWar', False);
      VoidEnabled := ReadBoolean(RootNode, 'VoidEnabled', False);
      VoidPercentage := ReadInteger(RootNode, 'VoidPercentage', 0);
      MaxNeutralUnits := ReadInteger(RootNode, 'MaxNeutralUnits', 3);
      GrowCells := TGrowCells(ReadInteger(RootNode, 'GrowCells', Integer(gcNone)));
      GrowAmount := TGrowAmount(ReadInteger(RootNode, 'GrowAmount', Integer(gaByOne)));
      CityEnabled := ReadBoolean(RootNode, 'CityEnabled', False);
      CityPercentage := ReadInteger(RootNode, 'CityPercentage', 0);
      BridgeEnabled := ReadBoolean(RootNode, 'BridgeEnabled', False);
      TurnCounter := ReadInteger(RootNode, 'TurnCounter', 0);
      WinObjective := TWinObjective(ReadInteger(RootNode, 'WinObjective', Integer(woDefeatAllOponents)));
      ReadInteger(RootNode, 'StayAliveForDefinedTurns', StayAliveForDefinedTurns);

      NewNode := FindNode('Map');
      if Assigned(NewNode) then
        Map.LoadFromNode(NewNode);

      NewNode := FindNode('Players');
      if Assigned(NewNode) then
        Players.LoadFromNode(NewNode);
      if Players.Count > 0 then CurrentPlayer := TPlayer(Players[0])
        else CurrentPlayer := nil;

      InitClients;
      Map.Cells.FixRefId;

      for I := 0 to Players.Count - 1 do begin
        TPlayer(Players[I]).PlayerMap.Update;
        TPlayer(Players[I]).PlayerMap.CheckVisibility;
      end;
      Running := ReadBoolean(RootNode, 'Running', True);
    end;
  finally
    FreeAndNil(Doc);
  end;
end;

procedure TGame.SaveToFile(FileName: string);
var
  NewNode: TDOMNode;
  Doc: TXMLDocument;
  RootNode: TDOMNode;
begin
  Self.FileName := FileName;
  Doc := TXMLDocument.Create;
  with Doc do try
    RootNode := CreateElement('XtacticsGame');
    AppendChild(RootNode);
    with RootNode do begin
      WriteBoolean(RootNode, 'SymetricMap', SymetricMap);
      WriteBoolean(RootNode, 'FogOfWar', FogOfWar);
      WriteBoolean(RootNode, 'VoidEnabled', VoidEnabled);
      WriteInteger(RootNode, 'VoidPercentage', VoidPercentage);
      WriteInteger(RootNode, 'MaxNeutralUnits', MaxNeutralUnits);
      WriteInteger(RootNode, 'GrowCells', Integer(GrowCells));
      WriteInteger(RootNode, 'GrowAmount', Integer(GrowAmount));
      WriteBoolean(RootNode, 'CityEnabled', CityEnabled);
      WriteInteger(RootNode, 'CityPercentage', CityPercentage);
      WriteBoolean(RootNode, 'BridgeEnabled', BridgeEnabled);
      WriteInteger(RootNode, 'TurnCounter', TurnCounter);
      WriteInteger(RootNode, 'WinObjective', Integer(WinObjective));
      WriteInteger(RootNode, 'StayAliveForDefinedTurns', StayAliveForDefinedTurns);
      WriteBoolean(RootNode, 'Running', Running);

      NewNode := OwnerDocument.CreateElement('Map');
      AppendChild(NewNode);
      Map.SaveToNode(NewNode);

      NewNode := OwnerDocument.CreateElement('Players');
      AppendChild(NewNode);
      Players.SaveToNode(NewNode);
  end;
    ForceDirectoriesUTF8(ExtractFileDir(FileName));
    WriteXMLFile(Doc, FileName);
  finally
    FreeAndNil(Doc);
  end;
end;

procedure TGame.ComputePlayerStats;
var
  I: Integer;
  J: Integer;
begin
  for I := 0 to Players.Count - 1 do
  with TPlayer(Players[I]) do begin
    TotalUnits := 0;
    TotalCells := 0;
    TotalCities := 0;

    TotalDiscovered := 0;
    for J := 0 to PlayerMap.Cells.Count - 1 do
    with TPlayerCell(PlayerMap.Cells[J]) do begin
      if Explored then Inc(TotalDiscovered);
    end;
  end;

  Map.ComputePlayerStats;
end;

procedure TGame.RecordTurnStats;
var
  I: Integer;
  NewStat: TGameTurnStat;
begin
  for I := 0 to Players.Count - 1 do
  with TPlayer(Players[I]) do begin
    NewStat := TGameTurnStat.Create;
    NewStat.DiscoveredCells := TotalDiscovered;
    NewStat.OccupiedCells := TotalCells;
    NewStat.Units := TotalUnits;
    NewStat.Cities := TotalCities;
    TurnStats.Add(NewStat);
  end;
end;

function TPlayers.GetAlivePlayers: TPlayerArray;
var
  Player: TPlayer;
begin
  SetLength(Result, 0);
  for Player in Self do
    if Player.IsAlive then begin
      SetLength(Result, Length(Result) + 1);
      Result[Length(Result) - 1] := Player;
    end;
end;

function TPlayers.GetAlivePlayersWithCities: TPlayerArray;
var
  Player: TPlayer;
begin
  SetLength(Result, 0);
  for Player in Self do
    if Player.TotalCities > 0 then begin
      SetLength(Result, Length(Result) + 1);
      Result[Length(Result) - 1] := Player;
    end;
end;

procedure TGame.NextTurn;
var
  PrevPlayer: TPlayer;
begin
  //TODO CurrentPlayer.View.SelectedCell := nil;
  CurrentPlayer.MoveAll;
  Map.Grow(CurrentPlayer);
  CurrentPlayer.UpdateRepeatMoves;
  ComputePlayerStats;
  PrevPlayer := CurrentPlayer;
  // Skip dead players
  repeat
    CurrentPlayer := TPlayer(Players[(Players.IndexOf(CurrentPlayer) + 1) mod Players.Count]);
    if Assigned(FOnPlayerChange) then
      FOnPlayerChange(Self);
  until CurrentPlayer.IsAlive;
  if Players.IndexOf(CurrentPlayer) < Players.IndexOf(PrevPlayer) then begin
    Inc(TurnCounter);
    RecordTurnStats;
    if Assigned(FOnNewTurn) then
      FOnNewTurn(Self);
  end;
  CheckWinObjective;
  CurrentPlayer.PlayerMap.CheckVisibility;
  CurrentPlayer.ReduceMovesPower;
  // For computers take view from previous human
  //if CurrentPlayer.Mode = pmComputer then CurrentPlayer.View.Assign(PrevPlayer.View);
end;

procedure TGame.CheckWinObjective;
var
  AlivePlayers: TPlayerArray;
  Winner: TPlayer;
begin
  Winner := nil;
  if WinObjective = woDefeatAllOponents then begin
    AlivePlayers := Players.GetAlivePlayers;
    if (Length(AlivePlayers) <= 1) then begin
      if Length(AlivePlayers) > 0 then Winner := TPlayer(AlivePlayers[0]);
      EndGame(Winner);
    end;
  end else
  if WinObjective = woDefeatAllOponentsCities then begin
    AlivePlayers := Players.GetAlivePlayersWithCities;
    if (Length(AlivePlayers) <= 1) then begin
      if Length(AlivePlayers) > 0 then Winner := TPlayer(AlivePlayers[0]);
      EndGame(Winner);
    end;
  end else
  if WinObjective = woSpecialCaptureCell then begin
    if Assigned(SpecialCaptureCell) and Assigned(SpecialCaptureCell.Player) then
      EndGame(SpecialCaptureCell.Player);
  end else
  if WinObjective = woStayAliveForDefinedTurns then begin
    // TODO: Not only one can win but multiple human players can survive.
    if TurnCounter > StayAliveForDefinedTurns then
      EndGame(nil);
  end;
end;

constructor TGame.Create;
begin
  Map := TMap.Create;
  Players := TPlayers.Create;
  Players.Game := Self;
  PlayersSetting := TPlayers.Create;
  PlayersSetting.Game := Self;
  Clients := TClients.Create;
  Clients.Game := Self;

  MapImageFileName := 'Images/Maps/WorldMap.png';
  Randomize;
  InitDefaultPlayersSetting;

  VoidEnabled := True;
  VoidPercentage := 20;
  MaxNeutralUnits := 4;

  Map.Game := Self;
  Map.Size := TPoint.Create(3, 3);
end;

destructor TGame.Destroy;
begin
  FreeAndNil(Clients);
  FreeAndNil(PlayersSetting);
  FreeAndNil(Players);
  FreeAndNil(Map);
  inherited Destroy;
end;

procedure TGame.Clear;
begin
  Clients.Clear;
  Players.Clear;
  Map.Clear;
end;

procedure TGame.New;
var
  I: Integer;
  Player: TPlayer;
begin
  Clear;
  FileName := SNewGameFile;
  TurnCounter := 1;

  Map.Generate;
  BuildTerrain;

  // Build bridges
  if BridgeEnabled then Map.CreateLinks;

  if SymetricMap then begin
    Map.MakeSymetric;
    if BridgeEnabled then Map.CreateLinks;
  end;

  Players.Assign(PlayersSetting);
  for Player in Players do Player.StartCell := nil;
  I := 0;
  for Player in Players do
  with Player do begin
    PlayerMap.Update;
    if (Map.Size.X > 0) and (Map.Size.Y > 0) then begin
      SelectPlayerStartCell(Player);
      if Assigned(Player.StartCell) then begin
        if SymetricMap and (I = 1) then
          StartCell := TCell(Map.Cells[Map.Cells.Count - 1 - Map.Cells.IndexOf(TPlayer(Players[0]).StartCell)]);

        StartCell.Terrain := ttCity;
        StartCell.Player := Player;
        StartCell.Power := Player.StartUnits;
      end;
    end;
    PlayerMap.CheckVisibility;
    Inc(I);
  end;
  if Players.Count > 0 then CurrentPlayer := TPlayer(Players[0])
    else CurrentPlayer := nil;

  InitClients;
  ComputePlayerStats;
end;

procedure TGame.EndGame(Winner: TPlayer = nil);
begin
  Running := False;
  if Assigned(OnWin) and Assigned(Winner) then OnWin(Winner);
end;

procedure TMap.Paint(Canvas: TCanvas; View: TView);
var
  I: Integer;
  Cell: TCell;
  PosFrom, PosTo: TPoint;
  Angle: Double;
  ArrowCenter: TPoint;
  Move: TUnitMove;
  CellLink: TCellLink;
begin
  with Canvas, View do
  try
    Lock;

    // Draw cell links
    Pen.Color := clBlack;
    Pen.Style := psSolid;
    Pen.Width := 3;
    for CellLink in CellLinks do
    with CellLink do begin
      if Length(Points) >= 2 then begin
        MoveTo(PointToStdPoint(View.CellToCanvasPos(Points[0])));
        for I := 1 to Length(Points) - 1 do
          LineTo(PointToStdPoint(View.CellToCanvasPos(Points[I])));
      end;
    end;

    // Draw cells
    for Cell in Cells do begin
      if (Cell.Terrain <> ttVoid) and Cell.IsVisible(View) then begin
        if Assigned(SelectedCell) and (SelectedCell = Cell) then
            Brush.Color := clGreen
          else if Assigned(SelectedCell) and IsCellsNeighbor(SelectedCell, Cell) then
            Brush.Color := clPurple
          else Brush.Color := Cell.GetColor;
        //Pen.Color := clBlack;
        PaintCell(Canvas, Cell.PosPx, IntToStr(Cell.GetAvialPower), View, Cell);
      end;

    end;

    // Draw arrows
    Pen.Color := clCream;
    for Move in Game.CurrentPlayer.Moves do begin
      PosFrom := CellToPos(Move.CellFrom);
      PosTo := CellToPos(Move.CellTo);
      if Move.CountRepeat > 0 then Pen.Width := 2
        else Pen.Width := 1;
      Angle := ArcTan((PosTo.Y - PosFrom.Y) / (PosTo.X - PosFrom.X));
      if Sign(PosTo.X - PosFrom.X) = -1 then Angle := Angle + Pi;
      ArrowCenter := View.CellToCanvasPos(TPoint.Create(Trunc(PosFrom.X + (PosTo.X - PosFrom.X) / 2),
        Trunc(PosFrom.Y + (PosTo.Y - PosFrom.Y) / 2)));
      DrawArrow(Canvas, View, ArrowCenter,
        Angle, IntToStr(Move.CountOnce));
    end;
  finally
    Unlock;
  end;
end;

end.
