close Warning: Can't synchronize with repository "(default)" (No changeset 184 in the repository). Look in the Trac log for more information.

source: tags/1.1.0/UGame.pas

Last change on this file was 131, checked in by chronos, 7 years ago
  • Modified: Allow cells to have more then 99 units. Additional units over 99 will die according twice of root square of units over 99. This allows to quick resolution even in case of narrow connections where two players send maximum units against each other.
File size: 90.6 KB
Line 
1unit UGame;
2
3{$mode delphi}{$H+}
4
5interface
6
7uses
8 Classes, SysUtils, ExtCtrls, Graphics, Contnrs, XMLConf, XMLRead, XMLWrite,
9 DOM, Math, LazFileUtils, UXMLUtils, Dialogs, Types, LCLType, LCLIntf;
10
11const
12 DefaultPlayerStartUnits = 5;
13 HexCellMulX = 1.12;
14 HexCellMulY = 1.292;
15 SquareCellMulX = 1.05;
16 SquareCellMulY = 1.05;
17 TriangleCellMulX = 0.55;
18 TriangleCellMulY = 1.05;
19 MaxPlayerCount = 8;
20
21type
22 TGame = class;
23 TPlayer = class;
24 TView = class;
25 TUnitMoves = class;
26 TCells = class;
27 TMap = class;
28 TCellLinks = class;
29 TMapArea = class;
30 TClient = class;
31
32 TFloatPoint = record
33 X, Y: Double;
34 end;
35
36 TPointArray = array of TPoint;
37
38 TTerrainType = (ttVoid, ttNormal, ttCity);
39
40 { TCell }
41
42 TCell = class
43 private
44 FArea: TMapArea;
45 FMap: TMap;
46 FPower: Integer;
47 procedure SetArea(AValue: TMapArea);
48 procedure SetPower(AValue: Integer);
49 public
50 Id: Integer;
51 PosPx: TPoint;
52 Polygon: TPointArray;
53 Terrain: TTerrainType;
54 PlayerId: Integer;
55 Player: TPlayer;
56 MovesFrom: TUnitMoves;
57 MovesTo: TUnitMoves;
58 NeighborsId: array of Integer;
59 Neighbors: TCells;
60 Mark: Boolean;
61 Links: TCellLinks;
62 procedure AreaExtend;
63 procedure FixRefId;
64 procedure LoadFromNode(Node: TDOMNode);
65 procedure SaveToNode(Node: TDOMNode);
66 procedure Assign(Source: TCell);
67 function IsVisible(View: TView): Boolean;
68 function GetColor: TColor;
69 function GetAvialPower: Integer;
70 function GetAttackPower: Integer;
71 constructor Create;
72 destructor Destroy; override;
73 property Power: Integer read FPower write SetPower;
74 property Map: TMap read FMap write FMap;
75 property Area: TMapArea read FArea write SetArea;
76 end;
77
78 TCellArray = array of TCell;
79
80 { TCells }
81
82 TCells = class(TObjectList)
83 Map: TMap;
84 procedure FixRefId;
85 function FindById(Id: Integer): TCell;
86 procedure LoadFromNode(Node: TDOMNode);
87 procedure SaveToNode(Node: TDOMNode);
88 procedure ClearMark;
89 end;
90
91 { TCellLink }
92
93 TCellLink = class
94 Points: array of TPoint;
95 Cells: TCells;
96 Map: TMap;
97 procedure LoadFromNode(Node: TDOMNode);
98 procedure SaveToNode(Node: TDOMNode);
99 constructor Create;
100 destructor Destroy; override;
101 end;
102
103 { TCellLinks }
104
105 TCellLinks = class(TObjectList)
106 Map: TMap;
107 function FindByCells(Cell1, Cell2: TCell): TCellLink;
108 procedure LoadFromNode(Node: TDOMNode);
109 procedure SaveToNode(Node: TDOMNode);
110 end;
111
112 { TView }
113
114 TView = class
115 private
116 FDestRect: TRect;
117 FZoom: Double;
118 procedure SetDestRect(AValue: TRect);
119 procedure SetZoom(AValue: Double);
120 public
121 Game: TGame;
122 SourceRect: TRect;
123 FocusedCell: TCell;
124 SelectedCell: TCell;
125 procedure Clear;
126 constructor Create;
127 destructor Destroy; override;
128 procedure SelectCell(Pos: TPoint; Player: TPlayer; ShiftState: TShiftState);
129 procedure CenterMap;
130 procedure CenterPlayerCity(Player: TPlayer);
131 function CanvasToCellPos(Pos: TPoint): TPoint;
132 function CellToCanvasPos(Pos: TPoint): TPoint;
133 function CanvasToCellRect(Pos: TRect): TRect;
134 function CellToCanvasRect(Pos: TRect): TRect;
135 procedure Assign(Source: TView);
136 property DestRect: TRect read FDestRect write SetDestRect;
137 property Zoom: Double read FZoom write SetZoom;
138 end;
139
140 { TCanvasEx }
141
142 TCanvasEx = class(TCanvas)
143 class procedure TextOutEx(Canvas: TCanvas; X,Y: Integer; const Text: string; MovePen: Boolean = True);
144 class procedure PolygonEx(Canvas: TCanvas; const Points: array of TPoint; Winding: Boolean);
145 end;
146
147 TMapShape = (msRectangle, msImage);
148
149 { TMapArea }
150
151 TMapArea = class
152 Id: Integer;
153 Map: TMap;
154 BridgeCount: Integer;
155 Cells: TCells;
156 procedure GetBorderCells(List: TCells);
157 constructor Create;
158 destructor Destroy; override;
159 end;
160
161 TMapAreas = class(TObjectList)
162 end;
163
164 { TMap }
165
166 TMap = class
167 private
168 FSize: TPoint;
169 function GetSize: TPoint; virtual;
170 procedure PaintCell(Canvas: TCanvas; Pos: TPoint; Text: string; View: TView;
171 Cell: TCell);
172 procedure DrawArrow(Canvas: TCanvas; View: TView; Pos: TPoint; Angle: Double;
173 Text: string);
174 procedure SetSize(AValue: TPoint); virtual;
175 protected
176 FNewCellId: Integer;
177 function GetNewCellId: Integer; virtual;
178 public
179 Game: TGame;
180 MaxPower: Integer;
181 DefaultCellSize: TPoint;
182 Cells: TCells;
183 Shape: TMapShape;
184 Image: TImage;
185 CellLinks: TCellLinks;
186 Areas: TMapAreas;
187 function IsOutsideShape(Coord: TPoint): Boolean; virtual;
188 function IsCellsNeighbor(Cell1, Cell2: TCell): Boolean; virtual;
189 function IsValidIndex(Index: TPoint): Boolean; virtual;
190 procedure Assign(Source: TMap); virtual;
191 procedure LoadFromFile(FileName: string); virtual;
192 procedure SaveToFile(FileName: string); virtual;
193 procedure LoadFromNode(Node: TDOMNode);
194 procedure SaveToNode(Node: TDOMNode);
195 function PosToCell(Pos: TPoint; View: TView): TCell; virtual;
196 function CellToPos(Cell: TCell): TPoint; virtual;
197 procedure Grow(APlayer: TPlayer); virtual;
198 procedure ComputePlayerStats; virtual;
199 procedure Generate; virtual;
200 constructor Create; virtual;
201 destructor Destroy; override;
202 procedure Paint(Canvas: TCanvas; View: TView); virtual;
203 function GetPixelRect: TRect; virtual;
204 procedure ForEachCells(Method: TMethod); virtual;
205 property Size: TPoint read GetSize write SetSize;
206 end;
207
208 TPlayerCells = class;
209 TPlayerMap = class;
210
211 { TPlayerCell }
212
213 TPlayerCell = class
214 Explored: Boolean;
215 InVisibleRange: Boolean;
216 MapCell: TCell;
217 List: TPlayerCells;
218 procedure LoadFromNode(Node: TDOMNode);
219 procedure SaveToNode(Node: TDOMNode);
220 end;
221
222 { TPlayerCells }
223
224 TPlayerCells = class(TObjectList)
225 Map: TPlayerMap;
226 function SearchCell(Cell: TCell): TPlayerCell;
227 procedure LoadFromNode(Node: TDOMNode);
228 procedure SaveToNode(Node: TDOMNode);
229 end;
230
231 { TPlayerMap }
232
233 TPlayerMap = class
234 Cells: TPlayerCells;
235 Player: TPlayer;
236 procedure LoadFromNode(Node: TDOMNode);
237 procedure SaveToNode(Node: TDOMNode);
238 procedure Update;
239 constructor Create;
240 destructor Destroy; override;
241 procedure CheckVisibility;
242 procedure Paint(Canvas: TCanvas; View: TView);
243 end;
244
245 { TGameTurnStat }
246
247 TGameTurnStat = class
248 OccupiedCells: Integer;
249 Units: Integer;
250 DiscoveredCells: Integer;
251 Cities: Integer;
252 procedure LoadFromNode(Node: TDOMNode);
253 procedure SaveToNode(Node: TDOMNode);
254 end;
255
256 { TGameTurnStats }
257
258 TGameTurnStats = class(TObjectList)
259 procedure LoadFromNode(Node: TDOMNode);
260 procedure SaveToNode(Node: TDOMNode);
261 end;
262
263 TPlayerMode = (pmHuman, pmComputer);
264 TComputerAgressivity = (caLow, caMedium, caHigh);
265
266 { TPlayer }
267
268 TPlayer = class
269 private
270 FClient: TClient;
271 FGame: TGame;
272 procedure SetClient(AValue: TClient);
273 procedure SetGame(AValue: TGame);
274 public
275 Id: Integer;
276 Name: string;
277 Color: TColor;
278 Mode: TPlayerMode;
279 TotalUnits: Integer;
280 TotalCells: Integer;
281 TotalCities: Integer;
282 TotalDiscovered: Integer;
283 StartUnits: Integer;
284 StartCell: TCell;
285 PlayerMap: TPlayerMap;
286 Defensive: Boolean;
287 Agressivity: TComputerAgressivity;
288 TurnStats: TGameTurnStats;
289 procedure Clear;
290 procedure LoadFromNode(Node: TDOMNode);
291 procedure SaveToNode(Node: TDOMNode);
292 procedure Paint(Canvas: TCanvas; View: TView);
293 constructor Create;
294 destructor Destroy; override;
295 procedure Assign(Source: TPlayer);
296 property Game: TGame read FGame write SetGame;
297 property Client: TClient read FClient write SetClient;
298 end;
299
300
301 { TComputer }
302
303 TComputer = class
304 Game: TGame;
305 Targets: TObjectList;
306 CellProcessDirection: Boolean;
307 procedure AttackNeutral;
308 procedure AttackPlayers;
309 procedure InnerMoves;
310 procedure IncreaseMoves;
311 procedure Process;
312 procedure FallBack;
313 function AttackersCount(Cell: TCell): Integer;
314 end;
315
316 TPlayerArray = array of TPlayer;
317
318 { TPlayers }
319
320 TPlayers = class(TObjectList)
321 Game: TGame;
322 NewPlayerId: Integer;
323 function FindById(Id: Integer): TPlayer;
324 procedure New(Name: string; Color: TColor; Mode: TPlayerMode);
325 function GetNewPlayerId: Integer;
326 procedure LoadFromNode(Node: TDOMNode);
327 procedure SaveToNode(Node: TDOMNode);
328 constructor Create;
329 function GetFirstHuman: TPlayer;
330 end;
331
332 { TUnitMove }
333
334 TUnitMove = class
335 private
336 FCellFrom: TCell;
337 FCellTo: TCell;
338 procedure SetCellFrom(AValue: TCell);
339 procedure SetCellTo(AValue: TCell);
340 public
341 List: TUnitMoves;
342 CountOnce: Integer;
343 CountRepeat: Integer;
344 procedure LoadFromNode(Node: TDOMNode);
345 procedure SaveToNode(Node: TDOMNode);
346 constructor Create;
347 destructor Destroy; override;
348 property CellFrom: TCell read FCellFrom write SetCellFrom;
349 property CellTo: TCell read FCellTo write SetCellTo;
350 end;
351
352 { TUnitMoves }
353
354 TUnitMoves = class(TObjectList)
355 Game: TGame;
356 procedure LoadFromNode(Node: TDOMNode);
357 procedure SaveToNode(Node: TDOMNode);
358 end;
359
360 { TClient }
361
362 TClient = class
363 private
364 FGame: TGame;
365 FControlPlayer: TPlayer;
366 procedure SetControlPlayer(AValue: TPlayer);
367 procedure SetGame(AValue: TGame);
368 public
369 Name: string;
370 View: TView;
371 constructor Create;
372 destructor Destroy; override;
373 property ControlPlayer: TPlayer read FControlPlayer write SetControlPlayer;
374 property Game: TGame read FGame write SetGame;
375 end;
376
377 { TClients }
378
379 TClients = class(TObjectList)
380 Game: TGame;
381 procedure New(Name: string);
382 end;
383
384 { TGame }
385
386 TMoveEvent = procedure(CellFrom, CellTo: TCell; var CountOnce, CountRepeat: Integer;
387 Update: Boolean; var Confirm: Boolean) of object;
388 TWinEvent = procedure(Player: TPlayer) of object;
389 TGrowAmount = (gaByOne, gaBySquareRoot);
390 TGrowCells = (gcNone, gcPlayerCities, gcPlayerAll);
391 TMapType = (mtNone, mtHexagon, mtSquare, mtTriangle, mtVoronoi);
392 TWinObjective = (woDefeatAllOponents, woDefeatAllOponentsCities,
393 woSpecialCaptureCell, woStayAliveForDefinedTurns);
394
395 TGame = class
396 private
397 FMapType: TMapType;
398 FOnMove: TMoveEvent;
399 FOnNewTurn: TNotifyEvent;
400 FOnPlayerChange: TNotifyEvent;
401 FOnWin: TWinEvent;
402 FRunning: Boolean;
403 LoadedImageFileName: string;
404 ProbabilityMatrix: array of array of Single;
405 procedure Attack(var AttackPower, DefendPower: Integer);
406 procedure MoveAll(Player: TPlayer);
407 procedure ClearMovesFromCell(Cell: TCell);
408 procedure RecordTurnStats;
409 procedure SetMapType(AValue: TMapType);
410 function SetMove(CellFrom, CellTo: TCell; Power: Integer; Confirmation: Boolean = True): TUnitMove;
411 procedure SetRunning(AValue: Boolean);
412 procedure UpdateRepeatMoves(Player: TPlayer);
413 procedure CheckCounterMove(Move: TUnitMove);
414 function SearchDifferentCellArea(List: TCells; SourceArea, DestArea: TMapArea): TCell;
415 procedure BuildTerrain;
416 procedure BuildBridges;
417 procedure BuildMapAreas;
418 procedure InitClients;
419 public
420 Players: TPlayers;
421 Clients: TClients;
422 Map: TMap;
423 MapImageFileName: string;
424 VoidEnabled: Boolean;
425 VoidPercentage: Integer;
426 SymetricMap: Boolean;
427 GrowCells: TGrowCells;
428 GrowAmount: TGrowAmount;
429 CityEnabled: Boolean;
430 CityPercentage: Integer;
431 CurrentPlayer: TPlayer;
432 Moves: TUnitMoves;
433 TurnCounter: Integer;
434 WinObjective: TWinObjective;
435 SpecialCaptureCell: TCell;
436 StayAliveForDefinedTurns: Integer;
437 MaxNeutralUnits: Integer;
438 FileName: string;
439 FogOfWar: Boolean;
440 BridgeEnabled: Boolean;
441 function AttackProbability(AttackCount, DefendCount: Integer): Double;
442 procedure LoadConfig(Config: TXmlConfig; Path: string);
443 procedure SaveConfig(Config: TXmlConfig; Path: string);
444 procedure LoadFromFile(FileName: string);
445 procedure SaveToFile(FileName: string);
446 procedure ComputePlayerStats;
447 function GetAlivePlayers: TPlayerArray;
448 function GetAlivePlayersWithCities: TPlayerArray;
449 procedure NextTurn;
450 procedure CheckWinObjective;
451 constructor Create;
452 destructor Destroy; override;
453 procedure New;
454 procedure EndGame(Winner: TPlayer = nil);
455 property Running: Boolean read FRunning write SetRunning;
456 property MapType: TMapType read FMapType write SetMapType;
457 published
458 property OnMove: TMoveEvent read FOnMove write FOnMove;
459 property OnWin: TWinEvent read FOnWin write FOnWin;
460 property OnNewTurn: TNotifyEvent read FOnNewTurn write FOnNewTurn;
461 property OnPlayerChange: TNotifyEvent read FOnPlayerChange write FOnPlayerChange;
462 end;
463
464var
465 PlayerModeText: array[TPlayerMode] of string;
466
467const
468 clOrange = $009Aff;
469 PlayerColors: array[0..7] of TColor = (clBlue, clRed, clGreen, clOrange,
470 clPurple, clMaroon, clAqua, clFuchsia);
471 ComputerAggroProbability: array[TComputerAgressivity] of Single = (0.9, 0.7, 0.5);
472
473procedure InitStrings;
474function FloatPoint(AX, AY: Double): TFloatPoint;
475
476resourcestring
477 SPlayer = 'Player';
478
479
480implementation
481
482uses
483 UMap;
484
485resourcestring
486 SMinimumPlayers = 'You need at least two players';
487 SHuman = 'Human';
488 SComputer = 'Computer';
489 SCannotSetPlayerStartCells = 'Cannot choose start cell for player';
490 SWrongFileFormat = 'Wrong file format';
491 SUnfinishedBattle = 'Unfinished battle';
492 SNewGameFile = 'New game.xtg';
493 SZeroZoomNotAlowed = 'Zero zoom not allowed';
494
495procedure InitStrings;
496begin
497 PlayerModeText[pmHuman] := SHuman;
498 PlayerModeText[pmComputer] := SComputer;
499end;
500
501function FloatPoint(AX, AY: Double): TFloatPoint;
502begin
503 Result.X := AX;
504 Result.Y := AY;
505end;
506
507function RectEquals(A, B: TRect): Boolean;
508begin
509 Result := (A.Left = B.Left) and (A.Top = B.Top) and
510 (A.Right = B.Right) and (A.Bottom = B.Bottom);
511end;
512
513function PtInRect(const Rect: TRect; Pos: TPoint): Boolean;
514begin
515 Result := (Pos.X >= Rect.Left) and (Pos.Y >= Rect.Top) and
516 (Pos.X <= Rect.Right) and (Pos.Y <= Rect.Bottom);
517end;
518
519function PtInPoly(const Points: array of TPoint; Pos: TPoint): Boolean;
520var
521 Count, K, J : Integer;
522begin
523 Result := False;
524 Count := Length(Points) ;
525 J := Count - 1;
526 for K := 0 to Count - 1 do begin
527 if ((Points[K].Y <= Pos.Y) and (Pos.Y < Points[J].Y)) or
528 ((Points[J].Y <= Pos.Y) and (Pos.Y < Points[K].Y)) then
529 begin
530 if (Pos.X < (Points[j].X - Points[K].X) *
531 (Pos.Y - Points[K].Y) /
532 (Points[j].Y - Points[K].Y) + Points[K].X) then
533 Result := not Result;
534 end;
535 J := K;
536 end;
537end;
538
539function GetPolygonRect(Polygon: array of TPoint): TRect;
540var
541 I: Integer;
542begin
543 Result := Rect(High(Integer), High(Integer),
544 Low(Integer), Low(Integer));
545 for I := 0 to Length(Polygon) - 1 do
546 with Polygon[I] do begin
547 if X > Result.Right then
548 Result.Right := X;
549 if X < Result.Left then
550 Result.Left := X;
551 if Y > Result.Bottom then
552 Result.Bottom := Y;
553 if Y < Result.Top then
554 Result.Top := Y;
555 end;
556end;
557
558function HalfColor(Color: TColor): TColor;
559begin
560 Result :=
561 ((((Color shr 0) and $ff) shr 1) shl 0) or
562 ((((Color shr 8) and $ff) shr 1) shl 8) or
563 ((((Color shr 16) and $ff) shr 1) shl 16) or
564 ((((Color shr 24) and $ff) shr 0) shl 24);
565end;
566
567{ TGameTurnStat }
568
569procedure TGameTurnStat.LoadFromNode(Node: TDOMNode);
570begin
571 OccupiedCells := ReadInteger(Node, 'OccupiedCells', 0);
572 Units := ReadInteger(Node, 'Units', 0);
573 DiscoveredCells := ReadInteger(Node, 'DiscoveredCells', 0);
574 Cities := ReadInteger(Node, 'Cities', 0);
575end;
576
577procedure TGameTurnStat.SaveToNode(Node: TDOMNode);
578begin
579 WriteInteger(Node, 'OccupiedCells', OccupiedCells);
580 WriteInteger(Node, 'Units', Units);
581 WriteInteger(Node, 'DiscoveredCells', DiscoveredCells);
582 WriteInteger(Node, 'Cities', Cities);
583end;
584
585{ TGameTurnStats }
586
587procedure TGameTurnStats.LoadFromNode(Node: TDOMNode);
588var
589 Node2: TDOMNode;
590 NewTurnStat: TGameTurnStat;
591begin
592 Count := 0;
593 Node2 := Node.FirstChild;
594 while Assigned(Node2) and (Node2.NodeName = 'TurnStat') do begin
595 NewTurnStat := TGameTurnStat.Create;
596 NewTurnStat.LoadFromNode(Node2);
597 Add(NewTurnStat);
598 Node2 := Node2.NextSibling;
599 end;
600end;
601
602procedure TGameTurnStats.SaveToNode(Node: TDOMNode);
603var
604 I: Integer;
605 NewNode: TDOMNode;
606begin
607 for I := 0 to Count - 1 do begin;
608 NewNode := Node.OwnerDocument.CreateElement('TurnStat');
609 Node.AppendChild(NewNode);
610 TGameTurnStat(Items[I]).SaveToNode(NewNode);
611 end;
612end;
613
614{ TClients }
615
616procedure TClients.New(Name: string);
617var
618 NewClient: TClient;
619begin
620 NewClient := TClient.Create;
621 NewClient.Game := Game;
622 NewClient.Name := Name;
623 Add(NewClient);
624end;
625
626{ TClient }
627
628procedure TClient.SetGame(AValue: TGame);
629begin
630 if FGame = AValue then Exit;
631 FGame := AValue;
632 View.Game := AValue;
633end;
634
635procedure TClient.SetControlPlayer(AValue: TPlayer);
636begin
637 if FControlPlayer = AValue then Exit;
638 if Assigned(FControlPlayer) then
639 FControlPlayer.FClient := nil;
640 FControlPlayer := AValue;
641 if Assigned(FControlPlayer) then
642 FControlPlayer.FClient := Self;
643end;
644
645constructor TClient.Create;
646begin
647 View := TView.Create;
648end;
649
650destructor TClient.Destroy;
651begin
652 ControlPlayer := nil;
653 FreeAndNil(View);
654 inherited Destroy;
655end;
656
657{ TCellLink }
658
659procedure TCellLink.LoadFromNode(Node: TDOMNode);
660var
661 Node2: TDOMNode;
662 Node3: TDOMNode;
663begin
664 Node3 := Node.FindNode('Points');
665 if Assigned(Node3) then begin
666 SetLength(Points, 0);
667 Node2 := Node3.FirstChild;
668 while Assigned(Node2) and (Node2.NodeName = 'Point') do begin
669 SetLength(Points, Length(Points) + 1);
670 Points[High(Points)].X := ReadInteger(Node2, 'X', 0);
671 Points[High(Points)].Y := ReadInteger(Node2, 'Y', 0);
672 Node2 := Node2.NextSibling;
673 end;
674 end;
675end;
676
677procedure TCellLink.SaveToNode(Node: TDOMNode);
678var
679 NewNode: TDOMNode;
680 NewNode2: TDOMNode;
681 I: Integer;
682begin
683 NewNode := Node.OwnerDocument.CreateElement('Points');
684 Node.AppendChild(NewNode);
685 for I := 0 to Length(Points) - 1 do begin
686 NewNode2 := NewNode.OwnerDocument.CreateElement('Point');
687 NewNode.AppendChild(NewNode2);
688 WriteInteger(NewNode2, 'X', Points[I].X);
689 WriteInteger(NewNode2, 'Y', Points[I].Y);
690 end;
691end;
692
693constructor TCellLink.Create;
694begin
695 Cells := TCells.Create;
696 Cells.OwnsObjects := False;
697end;
698
699destructor TCellLink.Destroy;
700var
701 I: Integer;
702 LastState: Boolean;
703begin
704 for I := 0 to Cells.Count - 1 do begin
705 if TCell(Cells[I]).Neighbors.Remove(TCell(Cells[1 - I])) = -1 then
706 raise Exception.Create('Can''t remove cell from neighbour cell');
707 if TCell(Cells[I]).Links.Remove(Self) = -1 then
708 raise Exception.Create('Can''t remove cell from neighbour cell');
709 end;
710 FreeAndNil(Cells);
711 if Assigned(Map) then begin
712 // To remove itself from list we need disable owning to not be called twice
713 try
714 LastState := Map.CellLinks.OwnsObjects;
715 Map.CellLinks.OwnsObjects := False;
716 Map.CellLinks.Remove(Self);
717 finally
718 Map.CellLinks.OwnsObjects := LastState;
719 end;
720 end;
721 inherited Destroy;
722end;
723
724{ TCellLinks }
725
726function TCellLinks.FindByCells(Cell1, Cell2: TCell): TCellLink;
727var
728 I: Integer;
729begin
730 I := 0;
731 while (I < Count) do begin
732 if (TCellLink(Items[I]).Cells[0] = Cell1) and (TCellLink(Items[I]).Cells[1] = Cell2) then
733 Break;
734 if (TCellLink(Items[I]).Cells[0] = Cell2) and (TCellLink(Items[I]).Cells[1] = Cell1) then
735 Break;
736 Inc(I);
737 end;
738 if I < Count then Result := TCellLink(Items[I])
739 else Result := nil;
740end;
741
742procedure TCellLinks.LoadFromNode(Node: TDOMNode);
743var
744 Node2: TDOMNode;
745 NewCell: TCellLink;
746begin
747 Count := 0;
748 Node2 := Node.FirstChild;
749 while Assigned(Node2) and (Node2.NodeName = 'CellLink') do begin
750 NewCell := TCellLink.Create;
751 //NewCell.Map := Map;
752 NewCell.LoadFromNode(Node2);
753 Add(NewCell);
754 Node2 := Node2.NextSibling;
755 end;
756end;
757
758procedure TCellLinks.SaveToNode(Node: TDOMNode);
759var
760 I: Integer;
761 NewNode2: TDOMNode;
762begin
763 for I := 0 to Count - 1 do
764 with TCellLink(Items[I]) do begin
765 NewNode2 := Node.OwnerDocument.CreateElement('CellLink');
766 Node.AppendChild(NewNode2);
767 SaveToNode(NewNode2);
768 end;
769end;
770
771{ TMapArea }
772
773procedure TMapArea.GetBorderCells(List: TCells);
774var
775 I: Integer;
776 J: Integer;
777 NeighVoidCount: Integer;
778begin
779 List.Clear;
780
781 Map.Cells.ClearMark;
782
783 for I := 0 to Cells.Count - 1 do
784 with TCell(Cells[I]) do begin
785 NeighVoidCount := 0;
786 for J := 0 to Neighbors.Count - 1 do
787 with TCell(Neighbors[J]) do
788 if (Terrain = ttVoid) then Inc(NeighVoidCount);
789
790 if (NeighVoidCount > 0) and (Area = Self) and (not Mark) then begin
791 List.Add(TCell(Self.Cells[I]));
792 Mark := True;
793 end;
794 end;
795end;
796
797constructor TMapArea.Create;
798begin
799 Cells := TCells.Create;
800 Cells.OwnsObjects := False;
801end;
802
803destructor TMapArea.Destroy;
804begin
805 FreeAndNil(Cells);
806 inherited Destroy;
807end;
808
809{ TPlayerCell }
810
811procedure TPlayerCell.LoadFromNode(Node: TDOMNode);
812begin
813 Explored := ReadBoolean(Node, 'Explored', False);
814 MapCell := List.Map.Player.Game.Map.Cells.FindById(ReadInteger(Node, 'MapCell', 0));
815end;
816
817procedure TPlayerCell.SaveToNode(Node: TDOMNode);
818begin
819 WriteBoolean(Node, 'Explored', Explored);
820 WriteInteger(Node, 'MapCell', MapCell.Id);
821end;
822
823{ TPlayerCells }
824
825function TPlayerCells.SearchCell(Cell: TCell): TPlayerCell;
826var
827 I: Integer;
828begin
829 I := 0;
830 while (I < Count) and (TPlayerCell(Items[I]).MapCell <> Cell) do Inc(I);
831 if I < Count then Result := TPlayerCell(Items[I])
832 else Result := nil;
833end;
834
835procedure TPlayerCells.LoadFromNode(Node: TDOMNode);
836var
837 Node2: TDOMNode;
838 NewCell: TPlayerCell;
839begin
840 Count := 0;
841 Node2 := Node.FirstChild;
842 while Assigned(Node2) and (Node2.NodeName = 'Cell') do begin
843 NewCell := TPlayerCell.Create;
844 NewCell.List := Self;
845 NewCell.LoadFromNode(Node2);
846 Add(NewCell);
847 Node2 := Node2.NextSibling;
848 end;
849end;
850
851procedure TPlayerCells.SaveToNode(Node: TDOMNode);
852var
853 I: Integer;
854 NewNode: TDOMNode;
855begin
856 for I := 0 to Count - 1 do begin;
857 NewNode := Node.OwnerDocument.CreateElement('Cell');
858 Node.AppendChild(NewNode);
859 TPlayerCell(Items[I]).SaveToNode(NewNode);
860 end;
861end;
862
863{ TPlayerMap }
864
865procedure TPlayerMap.LoadFromNode(Node: TDOMNode);
866var
867 NewNode: TDOMNode;
868begin
869 with Node do begin
870 NewNode := FindNode('Cells');
871 if Assigned(NewNode) then
872 Cells.LoadFromNode(NewNode);
873 end;
874end;
875
876procedure TPlayerMap.SaveToNode(Node: TDOMNode);
877var
878 NewNode: TDOMNode;
879begin
880 with Node do begin
881 NewNode := OwnerDocument.CreateElement('Cells');
882 AppendChild(NewNode);
883 Cells.SaveToNode(NewNode);
884 end;
885end;
886
887procedure TPlayerMap.Update;
888var
889 I: Integer;
890 OldCount: Integer;
891begin
892 // Update players cells count to map cells count to be 1:1
893 OldCount := Cells.Count;
894 Cells.Count := Player.Game.Map.Cells.Count;
895 for I := OldCount to Cells.Count - 1 do
896 Cells[I] := TPlayerCell.Create;
897
898 for I := 0 to Cells.Count - 1 do
899 with TPlayerCell(Cells[I]) do begin
900 List := Cells;
901 Explored := False;
902 InVisibleRange := False;
903 MapCell := TCell(Player.Game.Map.Cells[I]);
904 end;
905end;
906
907constructor TPlayerMap.Create;
908begin
909 Cells := TPlayerCells.Create;
910 Cells.Map := Self;
911end;
912
913destructor TPlayerMap.Destroy;
914begin
915 FreeAndNil(Cells);
916 inherited Destroy;
917end;
918
919procedure TPlayerMap.CheckVisibility;
920var
921 I: Integer;
922 C: Integer;
923 NeighCount: Integer;
924begin
925 for I := 0 to Cells.Count - 1 do
926 with TPlayerCell(Cells[I]) do begin
927 NeighCount := 0;
928 for C := 0 to MapCell.Neighbors.Count - 1 do
929 if TCell(MapCell.Neighbors[C]).Player = Player then
930 Inc(NeighCount);
931
932 InVisibleRange := (NeighCount > 0) or (TCell(MapCell).Player = Player);
933 if InVisibleRange and not Explored then Explored := True;
934 end;
935end;
936
937procedure TPlayerMap.Paint(Canvas: TCanvas; View: TView);
938var
939 I: Integer;
940 C: Integer;
941 Cell: TPlayerCell;
942 PosFrom, PosTo: TPoint;
943 Angle: Double;
944 ArrowCenter: TPoint;
945 Move: TUnitMove;
946 CellText: string;
947begin
948 with Canvas, View do
949 try
950 Lock;
951 // Draw cell links
952 Pen.Color := clBlack;
953 Pen.Style := psSolid;
954 Pen.Width := 3;
955 for C := 0 to Player.Game.Map.CellLinks.Count - 1 do
956 with TCellLink(Player.Game.Map.CellLinks[C]) do begin
957 if Length(Points) >= 2 then begin
958 MoveTo(View.CellToCanvasPos(Points[0]));
959 for I := 1 to Length(Points) - 1 do
960 LineTo(View.CellToCanvasPos(Points[I]));
961 end;
962 end;
963
964 // Draw cells
965 for C := 0 to Cells.Count - 1 do begin
966 Cell := TPlayerCell(Cells[C]);
967 if (Cell.MapCell.Terrain <> ttVoid) and Cell.MapCell.IsVisible(View) then begin
968 CellText := IntToStr(Cell.MapCell.GetAvialPower);
969 if Assigned(SelectedCell) and (SelectedCell = Cell.MapCell) then
970 Brush.Color := clGreen
971 else if Assigned(SelectedCell) and Player.Game.Map.IsCellsNeighbor(SelectedCell, Cell.MapCell) then
972 Brush.Color := clPurple
973 else if Player.Game.FogOfWar then begin
974 if Cell.InVisibleRange then begin
975 Brush.Color := Cell.MapCell.GetColor;
976 end else begin
977 if Cell.Explored then begin
978 Brush.Color := $404040;
979 CellText := '';
980 end else begin
981 Brush.Color := clBlack;
982 CellText := '';
983 end;
984 end;
985 end else Brush.Color := Cell.MapCell.GetColor;
986 Player.Game.Map.PaintCell(Canvas, Cell.MapCell.PosPx, CellText, View, Cell.MapCell);
987 end else
988 if Game.FogOfWar and (Cell.MapCell.Terrain = ttVoid) and (not Cell.Explored) then begin
989 Brush.Color := clBlack;
990 Player.Game.Map.PaintCell(Canvas, Cell.MapCell.PosPx, '', View, Cell.MapCell);
991 end;
992 end;
993
994 // Draw arrows
995 Pen.Color := clCream;
996 for I := 0 to Game.Moves.Count - 1 do begin
997 Move := TUnitMove(Game.Moves[I]);
998 PosFrom := Player.Game.Map.CellToPos(Move.CellFrom);
999 PosTo := Player.Game.Map.CellToPos(Move.CellTo);
1000 // In Fog of war mode show only
1001 if Game.FogOfWar and not Cells.SearchCell(Move.CellFrom).InVisibleRange and
1002 not Cells.SearchCell(Move.CellTo).InVisibleRange then
1003 Continue;
1004 if Move.CountRepeat > 0 then Pen.Width := 2
1005 else Pen.Width := 1;
1006 Angle := ArcTan((PosTo.Y - PosFrom.Y) / (PosTo.X - PosFrom.X));
1007 if Sign(PosTo.X - PosFrom.X) = -1 then Angle := Angle + Pi;
1008 ArrowCenter := View.CellToCanvasPos(Point(Trunc(PosFrom.X + (PosTo.X - PosFrom.X) / 2),
1009 Trunc(PosFrom.Y + (PosTo.Y - PosFrom.Y) / 2)));
1010 Player.Game.Map.DrawArrow(Canvas, View, ArrowCenter,
1011 Angle, IntToStr(Move.CountOnce));
1012 end;
1013 finally
1014 Unlock;
1015 end;
1016end;
1017
1018{ TCanvasEx }
1019
1020class procedure TCanvasEx.TextOutEx(Canvas: TCanvas; X, Y: Integer; const Text: string;
1021 MovePen: Boolean);
1022var
1023 Flags : Cardinal;
1024begin
1025 with Canvas do begin
1026 Changing;
1027 RequiredState([csHandleValid, csFontValid, csBrushValid]);
1028 Flags := 0;
1029 if TextStyle.Opaque then
1030 Flags := ETO_Opaque;
1031 ExtUTF8Out(Handle, X, Y, Flags, nil, PChar(Text), Length(Text), nil);
1032 if MovePen then MoveTo(X + TextWidth(Text), Y);
1033 Changed;
1034 end;
1035end;
1036
1037class procedure TCanvasEx.PolygonEx(Canvas: TCanvas; const Points: array of TPoint; Winding: Boolean);
1038begin
1039 //Changing;
1040 //RequiredState([csHandleValid, csBrushValid, csPenValid]);
1041 LCLIntf.Polygon(Canvas.Handle, @Points[0], Length(Points), Winding);
1042 //Changed;
1043end;
1044
1045
1046{ TCells }
1047
1048procedure TCells.FixRefId;
1049var
1050 I: Integer;
1051begin
1052 for I := 0 to Count - 1 do
1053 TCell(Items[I]).FixRefId;
1054end;
1055
1056function TCells.FindById(Id: Integer): TCell;
1057var
1058 I: Integer;
1059begin
1060 I := 0;
1061 while (I < Count) and (TCell(Items[I]).Id <> Id) do Inc(I);
1062 if I < Count then Result := TCell(Items[I])
1063 else Result := nil;
1064end;
1065
1066procedure TCells.LoadFromNode(Node: TDOMNode);
1067var
1068 Node2: TDOMNode;
1069 NewCell: TCell;
1070begin
1071 Count := 0;
1072 Node2 := Node.FirstChild;
1073 while Assigned(Node2) and (Node2.NodeName = 'Cell') do begin
1074 NewCell := TCell.Create;
1075 NewCell.Map := Map;
1076 NewCell.LoadFromNode(Node2);
1077 Add(NewCell);
1078 Node2 := Node2.NextSibling;
1079 end;
1080end;
1081
1082procedure TCells.SaveToNode(Node: TDOMNode);
1083var
1084 I: Integer;
1085 NewNode2: TDOMNode;
1086begin
1087 for I := 0 to Count - 1 do
1088 with TCell(Items[I]) do begin
1089 NewNode2 := Node.OwnerDocument.CreateElement('Cell');
1090 Node.AppendChild(NewNode2);
1091 SaveToNode(NewNode2);
1092 end;
1093end;
1094
1095procedure TCells.ClearMark;
1096var
1097 I: Integer;
1098begin
1099 for I := 0 to Count - 1 do
1100 TCell(Items[I]).Mark := False;
1101end;
1102
1103{ TPlayers }
1104
1105function TPlayers.FindById(Id: Integer): TPlayer;
1106var
1107 I: Integer;
1108begin
1109 I := 0;
1110 while (I < Count) and (TPlayer(Items[I]).Id <> Id) do Inc(I);
1111 if I < Count then Result := TPlayer(Items[I])
1112 else Result := nil;
1113end;
1114
1115procedure TPlayers.New(Name: string; Color: TColor; Mode: TPlayerMode);
1116var
1117 NewPlayer: TPlayer;
1118begin
1119 NewPlayer := TPlayer.Create;
1120 NewPlayer.Game := Game;
1121 NewPlayer.Name := Name;
1122 NewPlayer.Color := Color;
1123 NewPlayer.Mode := Mode;
1124 NewPlayer.Id := GetNewPlayerId;
1125 if Mode = pmComputer then
1126 NewPlayer.Agressivity := caMedium;
1127 Add(NewPlayer);
1128end;
1129
1130function TPlayers.GetNewPlayerId: Integer;
1131begin
1132 Result := NewPlayerId;
1133 Inc(NewPlayerId);
1134end;
1135
1136procedure TPlayers.LoadFromNode(Node: TDOMNode);
1137var
1138 Node2: TDOMNode;
1139 NewPlayer: TPlayer;
1140begin
1141 Count := 0;
1142 Node2 := Node.FirstChild;
1143 while Assigned(Node2) and (Node2.NodeName = 'Player') do begin
1144 NewPlayer := TPlayer.Create;
1145 NewPlayer.Game := Game;
1146 NewPlayer.LoadFromNode(Node2);
1147 Add(NewPlayer);
1148 Node2 := Node2.NextSibling;
1149 end;
1150end;
1151
1152procedure TPlayers.SaveToNode(Node: TDOMNode);
1153var
1154 I: Integer;
1155 NewNode: TDOMNode;
1156begin
1157 for I := 0 to Count - 1 do begin;
1158 NewNode := Node.OwnerDocument.CreateElement('Player');
1159 Node.AppendChild(NewNode);
1160 TPlayer(Items[I]).SaveToNode(NewNode);
1161 end;
1162end;
1163
1164constructor TPlayers.Create;
1165begin
1166 inherited Create;
1167 NewPlayerId := 1;
1168end;
1169
1170function TPlayers.GetFirstHuman: TPlayer;
1171var
1172 I: Integer;
1173begin
1174 I := 0;
1175 while (I < Count) and (TPlayer(Items[I]).Mode <> pmHuman) do Inc(I);
1176 if I < Count then Result := TPlayer(Items[I])
1177 else Result := nil;
1178end;
1179
1180{ TUnitMoves }
1181
1182procedure TUnitMoves.LoadFromNode(Node: TDOMNode);
1183var
1184 Node2: TDOMNode;
1185 NewUnitMove: TUnitMove;
1186begin
1187 Count := 0;
1188 Node2 := Node.FirstChild;
1189 while Assigned(Node2) and (Node2.NodeName = 'UnitMove') do begin
1190 NewUnitMove := TUnitMove.Create;
1191 NewUnitMove.List := Self;
1192 NewUnitMove.LoadFromNode(Node2);
1193 Add(NewUnitMove);
1194 Node2 := Node2.NextSibling;
1195 end;
1196end;
1197
1198procedure TUnitMoves.SaveToNode(Node: TDOMNode);
1199var
1200 I: Integer;
1201 NewNode: TDOMNode;
1202begin
1203 for I := 0 to Count - 1 do begin;
1204 NewNode := Node.OwnerDocument.CreateElement('UnitMove');
1205 Node.AppendChild(NewNode);
1206 TUnitMove(Items[I]).SaveToNode(NewNode);
1207 end;
1208end;
1209
1210{ TMap }
1211
1212function TMap.GetSize: TPoint;
1213begin
1214 Result:= FSize;
1215end;
1216
1217procedure TMap.SetSize(AValue: TPoint);
1218begin
1219 if (FSize.X <> AValue.X) or (FSize.Y <> AValue.Y) then begin
1220 FSize := AValue;
1221 Generate;
1222 end;
1223end;
1224
1225function TMap.GetNewCellId: Integer;
1226begin
1227 Result := FNewCellId;
1228 Inc(FNewCellId);
1229end;
1230
1231function TMap.IsOutsideShape(Coord: TPoint): Boolean;
1232var
1233 Rect: TRect;
1234 Color: TColor;
1235 Pos: TPoint;
1236begin
1237 case Shape of
1238 msRectangle: Result := False;
1239 msImage: begin
1240 Rect := GetPixelRect;
1241 with Image.Picture.Bitmap do begin
1242 Pos := Point(Trunc(Coord.X / (Rect.Right - Rect.Left) * Width),
1243 Trunc(Coord.Y / (Rect.Bottom - Rect.Top) * Height));
1244 Color := Canvas.Pixels[Pos.X, Pos.Y];
1245 end;
1246 Result := Color <> clWhite;
1247 end;
1248 else Result := False;
1249 end;
1250end;
1251
1252procedure TMap.DrawArrow(Canvas: TCanvas; View: TView; Pos: TPoint;
1253 Angle: Double; Text: string);
1254var
1255 Points: array of TPoint;
1256 FPoints: array of TFloatPoint;
1257 I: Integer;
1258 ArrowSize: TPoint;
1259begin
1260 Canvas.Brush.Color := clWhite;
1261 Canvas.Pen.Color := clBlack;
1262 SetLength(Points, 8);
1263 SetLength(FPoints, 8);
1264 ArrowSize := Point(Trunc(DefaultCellSize.X / 3 * View.Zoom),
1265 Trunc(DefaultCellSize.Y / 3 * View.Zoom));
1266 FPoints[0] := FloatPoint(+0.5 * ArrowSize.X, +0 * ArrowSize.Y);
1267 FPoints[1] := FloatPoint(+0 * ArrowSize.X, +0.5 * ArrowSize.Y);
1268 FPoints[2] := FloatPoint(+0 * ArrowSize.X, +0.25 * ArrowSize.Y);
1269 FPoints[3] := FloatPoint(-0.5 * ArrowSize.X, +0.25 * ArrowSize.Y);
1270 FPoints[4] := FloatPoint(-0.5 * ArrowSize.X, -0.25 * ArrowSize.Y);
1271 FPoints[5] := FloatPoint(+0 * ArrowSize.X, -0.25 * ArrowSize.Y);
1272 FPoints[6] := FloatPoint(+0 * ArrowSize.X, -0.5 * ArrowSize.Y);
1273 FPoints[7] := FloatPoint(+0.5 * ArrowSize.X, 0 * ArrowSize.Y);
1274 // Rotate
1275 for I := 0 to Length(Points) - 1 do
1276 FPoints[I] := FloatPoint(FPoints[I].X * Cos(Angle) - FPoints[I].Y * Sin(Angle),
1277 FPoints[I].X * Sin(Angle) + FPoints[I].Y * Cos(Angle));
1278 // Shift
1279 for I := 0 to Length(Points) - 1 do
1280 Points[I] := Point(Trunc(FPoints[I].X + Pos.X), Trunc(FPoints[I].Y + Pos.Y));
1281 with Canvas do begin
1282 Brush.Style := bsSolid;
1283 Polygon(Points);
1284 Brush.Style := bsClear;
1285 Font.Color := clBlack;
1286 Font.Size := Trunc(26 * View.Zoom);
1287 TextOut(Pos.X - TextWidth(Text) div 2,
1288 Pos.Y - TextHeight(Text) div 2, Text);
1289 Pen.Width := 1;
1290 end;
1291end;
1292
1293function TMap.IsCellsNeighbor(Cell1, Cell2: TCell): Boolean;
1294begin
1295 Result := Cell1.Neighbors.IndexOf(Cell2) <> -1;
1296end;
1297
1298function TMap.IsValidIndex(Index: TPoint): Boolean;
1299begin
1300 Result := False;
1301end;
1302
1303procedure TMap.Assign(Source: TMap);
1304var
1305 I: Integer;
1306begin
1307 MaxPower := Source.MaxPower;
1308 Game := Source.Game;
1309 Size := Source.Size;
1310 DefaultCellSize := Source.DefaultCellSize;
1311 Shape := Source.Shape;
1312 Image.Picture.Bitmap.Assign(Source.Image.Picture.Bitmap);
1313
1314 // TODO: How to copy cells
1315 {// Copy all cells
1316 Cells.Count := 0;
1317 Cells.Count := Source.Cells.Count;
1318 for I := 0 to Cells.Count - 1 do begin
1319 Cells[I] := TCell.Create;
1320 TCell(Cells[I]).Map := Self;
1321 TCell(Cells[I]).Assign(TCell(Source.Cells[I]));
1322 end;
1323 }
1324end;
1325
1326procedure TMap.LoadFromFile(FileName: string);
1327begin
1328
1329end;
1330
1331procedure TMap.SaveToFile(FileName: string);
1332begin
1333
1334end;
1335
1336procedure TMap.LoadFromNode(Node: TDOMNode);
1337var
1338 Node2: TDOMNode;
1339begin
1340 Size := Point(ReadInteger(Node, 'SizeX', 0), ReadInteger(Node, 'SizeY', 0));
1341 DefaultCellSize.X := ReadInteger(Node, 'DefaultCellSizeX', 1);
1342 DefaultCellSize.Y := ReadInteger(Node, 'DefaultCellSizeY', 1);
1343 MaxPower := ReadInteger(Node, 'MaxPower', 99);
1344 Shape := TMapShape(ReadInteger(Node, 'Shape', Integer(msRectangle)));
1345 Node2 := Node.FindNode('Cells');
1346 if Assigned(Node2) then
1347 Cells.LoadFromNode(Node2);
1348 Node2 := Node.FindNode('CellLinks');
1349 if Assigned(Node2) then
1350 CellLinks.LoadFromNode(Node2);
1351end;
1352
1353procedure TMap.SaveToNode(Node: TDOMNode);
1354var
1355 NewNode: TDOMNode;
1356 NewNode2: TDOMNode;
1357 I: Integer;
1358begin
1359 WriteInteger(Node, 'DefaultCellSizeX', DefaultCellSize.X);
1360 WriteInteger(Node, 'DefaultCellSizeY', DefaultCellSize.Y);
1361 WriteInteger(Node, 'MaxPower', MaxPower);
1362 WriteInteger(Node, 'Shape', Integer(Shape));
1363 WriteInteger(Node, 'SizeX', Size.X);
1364 WriteInteger(Node, 'SizeY', Size.Y);
1365 NewNode := Node.OwnerDocument.CreateElement('Cells');
1366 Node.AppendChild(NewNode);
1367 Cells.SaveToNode(NewNode);
1368 NewNode := Node.OwnerDocument.CreateElement('CellLinks');
1369 Node.AppendChild(NewNode);
1370 CellLinks.SaveToNode(NewNode);
1371end;
1372
1373function TMap.PosToCell(Pos: TPoint; View: TView): TCell;
1374var
1375 I: Integer;
1376begin
1377 Result := nil;
1378 for I := 0 to Cells.Count - 1 do
1379 if TCell(Cells[I]).Terrain <> ttVoid then begin
1380 if PtInPoly(TCell(Cells[I]).Polygon, Pos) then begin
1381 Result := TCell(Cells[I]);
1382 Exit;
1383 end;
1384 end;
1385end;
1386
1387function TMap.CellToPos(Cell: TCell): TPoint;
1388begin
1389 Result := Cell.PosPx;
1390end;
1391
1392procedure TMap.Grow(APlayer: TPlayer);
1393var
1394 I: Integer;
1395 Addition: Integer;
1396 Dies: Integer;
1397begin
1398 for I := 0 to Cells.Count - 1 do
1399 with TCell(Cells[I]) do begin
1400 if (Player = APlayer) and ((Game.GrowCells = gcPlayerAll) or
1401 ((Game.GrowCells = gcPlayerCities) and (Terrain = ttCity))) then begin
1402 if Power < MaxPower then begin
1403 // Increase units count
1404 if Game.GrowAmount = gaByOne then Addition := 1
1405 else if Game.GrowAmount = gaBySquareRoot then begin
1406 Addition := Trunc(Sqrt(Power));
1407 if Addition = 0 then Addition := 1;
1408 end;
1409 Power := Min(Power + Addition, MaxPower);
1410 end else
1411 if Power > MaxPower then begin
1412 // Reduce units count
1413 // If cell has more then MaxPower units then additional units dies
1414 // in twice of squeare root of unites over MaxPower
1415 Dies := 2 * Trunc(Sqrt(Power - MaxPower));
1416 Power := Max(Power - Dies, 0);
1417 end;
1418 end;
1419 end;
1420end;
1421
1422procedure TMap.PaintCell(Canvas: TCanvas; Pos: TPoint; Text: string; View: TView;
1423 Cell: TCell);
1424var
1425 I: Integer;
1426 TextPos: TPoint;
1427 Points: array of TPoint;
1428 TextSize: TSize;
1429begin
1430 with Canvas do begin
1431 if Assigned(View.FocusedCell) and (View.FocusedCell = Cell) then begin
1432 Pen.Color := clYellow;
1433 Pen.Style := psSolid;
1434 Pen.Width := 1;
1435 end else
1436 if Cell.Terrain = ttCity then begin
1437 // Cannot set clear border as it will display shifted on gtk2
1438 //Pen.Style := psClear;
1439 Pen.Color := clBlack;
1440 Pen.Style := psSolid;
1441 Pen.Width := 3;
1442 end else begin
1443 // Cannot set clear border as it will display shifted on gtk2
1444 //Pen.Style := psClear;
1445 Pen.Color := Brush.Color;
1446 Pen.Style := psSolid;
1447 Pen.Width := 0;
1448 end;
1449 // Transform view
1450 SetLength(Points, Length(Cell.Polygon));
1451 for I := 0 to Length(Points) - 1 do
1452 Points[I] := View.CellToCanvasPos(Cell.Polygon[I]);
1453 Brush.Style := bsSolid;
1454 //Polygon(Points, False, 0, Length(Points));
1455 TCanvasEx.PolygonEx(Canvas, Points, False);
1456 //MoveTo(Points[0].X, Points[0].Y);
1457 //LineTo(Points[1].X, Points[1].Y);
1458
1459 // Show cell text
1460 if Text <> '0' then begin
1461 Pen.Style := psSolid;
1462 Font.Color := clWhite;
1463 Brush.Style := bsClear;
1464 Font.Size := Trunc(42 * View.Zoom);
1465 TextPos := View.CellToCanvasPos(Pos);
1466 TextSize := TextExtent(Text);
1467 TCanvasEx.TextOutEx(Canvas, Round(TextPos.X) - TextSize.cx div 2,
1468 Round(TextPos.Y) - TextSize.cy div 2, Text, False);
1469 end;
1470 end;
1471end;
1472
1473procedure TMap.ComputePlayerStats;
1474var
1475 I: Integer;
1476begin
1477 for I := 0 to Cells.Count - 1 do
1478 with TCell(Cells[I]) do begin
1479 if Assigned(Player) then begin
1480 Player.TotalCells := Player.TotalCells + 1;
1481 Player.TotalUnits := Player.TotalUnits + Power;
1482 if Terrain = ttCity then
1483 Player.TotalCities := Player.TotalCities + 1;
1484 end;
1485 end;
1486end;
1487
1488procedure TMap.Generate;
1489var
1490 X, Y: Integer;
1491 I: Integer;
1492 NewCell: TCell;
1493begin
1494 // Free previous
1495 Cells.Count := 0;
1496 // Allocate and init new
1497 Cells.Count := FSize.Y * FSize.X;
1498 FNewCellId := 1;
1499 for Y := 0 to FSize.Y - 1 do
1500 for X := 0 to FSize.X - 1 do begin
1501 NewCell := TCell.Create;
1502 NewCell.Map := Self;
1503 NewCell.PosPx := Point(X, Y);
1504 NewCell.Id := GetNewCellId;
1505 Cells[Y * FSize.X + X] := NewCell;
1506 end;
1507end;
1508
1509constructor TMap.Create;
1510begin
1511 MaxPower := 99;
1512 DefaultCellSize := Point(220, 220);
1513 Cells := TCells.Create;
1514 Cells.Map := Self;
1515 Size := Point(0, 0);
1516 Image := TImage.Create(nil);
1517 CellLinks := TCellLinks.Create;
1518 CellLinks.Map := Self;
1519 Areas := TMapAreas.Create;
1520end;
1521
1522destructor TMap.Destroy;
1523begin
1524 Size := Point(0, 0);
1525 FreeAndNil(Areas);
1526 FreeAndNil(CellLinks);
1527 FreeAndNil(Image);
1528 FreeAndNil(Cells);
1529 inherited Destroy;
1530end;
1531
1532function TMap.GetPixelRect: TRect;
1533var
1534 I: Integer;
1535 CellRect: TRect;
1536begin
1537 Result := Rect(0, 0, 0, 0);
1538 // This is generic algorithm to determine pixel size of entire map
1539 for I := 0 to Cells.Count - 1 do begin
1540 CellRect := GetPolygonRect(TCell(Cells[I]).Polygon);
1541 if I = 0 then Result := CellRect
1542 else begin
1543 if CellRect.Right > Result.Right then Result.Right := CellRect.Right;
1544 if CellRect.Bottom > Result.Bottom then Result.Bottom := CellRect.Bottom;
1545 if CellRect.Left < Result.Left then Result.Left := CellRect.Left;
1546 if CellRect.Top < Result.Top then Result.Top := CellRect.Top;
1547 end;
1548 end;
1549end;
1550
1551procedure TMap.ForEachCells(Method: TMethod);
1552begin
1553
1554end;
1555
1556{ TUnitMove }
1557
1558procedure TUnitMove.SetCellFrom(AValue: TCell);
1559begin
1560 if FCellFrom = AValue then Exit;
1561 if Assigned(AValue) and not Assigned(FCellFrom) then begin
1562 AValue.MovesFrom.Add(Self);
1563 end else
1564 if not Assigned(AValue) and Assigned(FCellFrom) then begin
1565 FCellFrom.MovesFrom.Remove(Self);
1566 end;
1567 FCellFrom := AValue;
1568end;
1569
1570procedure TUnitMove.SetCellTo(AValue: TCell);
1571begin
1572 if FCellTo = AValue then Exit;
1573 if Assigned(AValue) and not Assigned(FCellTo) then begin
1574 AValue.MovesTo.Add(Self);
1575 end else
1576 if not Assigned(AValue) and Assigned(FCellTo) then begin
1577 FCellTo.MovesTo.Remove(Self);
1578 end;
1579 FCellTo := AValue;
1580end;
1581
1582procedure TUnitMove.LoadFromNode(Node: TDOMNode);
1583begin
1584 CountOnce := ReadInteger(Node, 'CountOnce', 0);
1585 CountRepeat := ReadInteger(Node, 'CountRepeat', 0);
1586 CellFrom := List.Game.Map.Cells.FindById(ReadInteger(Node, 'CellFrom', 0));
1587 CellTo := List.Game.Map.Cells.FindById(ReadInteger(Node, 'CellTo', 0));
1588end;
1589
1590procedure TUnitMove.SaveToNode(Node: TDOMNode);
1591begin
1592 WriteInteger(Node, 'CountOnce', CountOnce);
1593 WriteInteger(Node, 'CountRepeat', CountRepeat);
1594 WriteInteger(Node, 'CellFrom', CellFrom.Id);
1595 WriteInteger(Node, 'CellTo', CellTo.Id);
1596end;
1597
1598constructor TUnitMove.Create;
1599begin
1600 List := nil; // Is later set to parent list owning item
1601 FCellFrom := nil;
1602 FCellTo := nil;
1603end;
1604
1605destructor TUnitMove.Destroy;
1606var
1607 LastState: Boolean;
1608begin
1609 CellFrom := nil;
1610 CellTo := nil;
1611 if Assigned(List) then begin
1612 // To remove itself from list we need disable owning to not be called twice
1613 try
1614 LastState := List.OwnsObjects;
1615 List.OwnsObjects := False;
1616 List.Remove(Self);
1617 finally
1618 List.OwnsObjects := LastState;
1619 end;
1620 end;
1621 inherited Destroy;
1622end;
1623
1624{ TView }
1625
1626procedure TView.SetZoom(AValue: Double);
1627begin
1628 if FZoom = AValue then Exit;
1629 if AValue = 0 then
1630 raise Exception.Create(SZeroZoomNotAlowed);
1631 FZoom := AValue;
1632 SourceRect := Bounds(Trunc(SourceRect.Left + (SourceRect.Right - SourceRect.Left) div 2 - (DestRect.Right - DestRect.Left) / Zoom / 2),
1633 Trunc(SourceRect.Top + (SourceRect.Bottom - SourceRect.Top) div 2 - (DestRect.Bottom - DestRect.Top) / Zoom / 2),
1634 Trunc((DestRect.Right - DestRect.Left) / Zoom),
1635 Trunc((DestRect.Bottom - DestRect.Top) / Zoom));
1636end;
1637
1638procedure TView.Clear;
1639begin
1640 FocusedCell := nil;
1641 SelectedCell := nil;
1642end;
1643
1644procedure TView.SetDestRect(AValue: TRect);
1645var
1646 Diff: TPoint;
1647begin
1648 if RectEquals(FDestRect, AValue) then Exit;
1649 Diff := Point(Trunc((DestRect.Right - DestRect.Left) / Zoom - (AValue.Right - AValue.Left) / Zoom) div 2,
1650 Trunc((DestRect.Bottom - DestRect.Top) / Zoom - (AValue.Bottom - AValue.Top) / Zoom) div 2);
1651 FDestRect := AValue;
1652 SourceRect := Bounds(SourceRect.Left + Diff.X, SourceRect.Top + Diff.Y,
1653 Trunc((DestRect.Right - DestRect.Left) / Zoom),
1654 Trunc((DestRect.Bottom - DestRect.Top) / Zoom));
1655end;
1656
1657constructor TView.Create;
1658begin
1659 Zoom := 1.5;
1660 Clear;
1661end;
1662
1663destructor TView.Destroy;
1664begin
1665 inherited Destroy;
1666end;
1667
1668{ TCell }
1669
1670procedure TCell.SetPower(AValue: Integer);
1671begin
1672 if FPower = AValue then Exit;
1673 if AValue < 0 then
1674 raise Exception.Create('Not allowed to substract power under zero do negative value');
1675 FPower := AValue;
1676end;
1677
1678procedure TCell.SetArea(AValue: TMapArea);
1679begin
1680 if FArea = AValue then Exit;
1681 if Assigned(FArea) then FArea.Cells.Remove(Self);
1682 FArea := AValue;
1683 if Assigned(FArea) then FArea.Cells.Add(Self);
1684end;
1685
1686procedure TCell.AreaExtend;
1687var
1688 I: Integer;
1689begin
1690 for I := 0 to Neighbors.Count - 1 do
1691 with TCell(Neighbors[I]) do
1692 if (Terrain <> ttVoid) and (not Assigned(Area)) then begin
1693 Area := Self.Area;
1694 AreaExtend;
1695 end;
1696end;
1697
1698procedure TCell.FixRefId;
1699var
1700 I: Integer;
1701begin
1702 Player := Map.Game.Players.FindById(PlayerId);
1703
1704 Neighbors.Count := Length(NeighborsId);
1705 for I := 0 to Length(NeighborsId) - 1 do begin
1706 Neighbors[I] := Map.Cells.FindById(NeighborsId[I]);
1707 end;
1708end;
1709
1710procedure TCell.LoadFromNode(Node: TDOMNode);
1711var
1712 Node2: TDOMNode;
1713 Node3: TDOMNode;
1714 NewCell: TCell;
1715begin
1716 Id := ReadInteger(Node, 'Id', 0);
1717 Power := ReadInteger(Node, 'Power', 0);
1718 Terrain := TTerrainType(ReadInteger(Node, 'Terrain', Integer(ttVoid)));
1719 PosPx.X := ReadInteger(Node, 'PosX', 0);
1720 PosPx.Y := ReadInteger(Node, 'PosY', 0);
1721 PlayerId := ReadInteger(Node, 'Player', 0);
1722
1723 Node3 := Node.FindNode('Neighbours');
1724 if Assigned(Node3) then begin
1725 SetLength(NeighborsId, 0);
1726 Node2 := Node3.FirstChild;
1727 while Assigned(Node2) and (Node2.NodeName = 'Neighbour') do begin
1728 SetLength(NeighborsId, Length(NeighborsId) + 1);
1729 NeighborsId[High(NeighborsId)] := ReadInteger(Node2, 'Id', 0);
1730 Node2 := Node2.NextSibling;
1731 end;
1732 end;
1733
1734 Node3 := Node.FindNode('Polygon');
1735 if Assigned(Node3) then begin
1736 SetLength(Polygon, 0);
1737 Node2 := Node3.FirstChild;
1738 while Assigned(Node2) and (Node2.NodeName = 'Point') do begin
1739 SetLength(Polygon, Length(Polygon) + 1);
1740 Polygon[High(Polygon)].X := ReadInteger(Node2, 'X', 0);
1741 Polygon[High(Polygon)].Y := ReadInteger(Node2, 'Y', 0);
1742 Node2 := Node2.NextSibling;
1743 end;
1744 end;
1745end;
1746
1747procedure TCell.SaveToNode(Node: TDOMNode);
1748var
1749 NewNode: TDOMNode;
1750 NewNode2: TDOMNode;
1751 I: Integer;
1752begin
1753 WriteInteger(Node, 'Id', Id);
1754 WriteInteger(Node, 'Power', Power);
1755 WriteInteger(Node, 'Terrain', Integer(Terrain));
1756 WriteInteger(Node, 'PosX', PosPx.X);
1757 WriteInteger(Node, 'PosY', PosPx.Y);
1758 if Assigned(Player) then
1759 WriteInteger(Node, 'Player', Player.Id)
1760 else WriteInteger(Node, 'Player', 0);
1761 NewNode := Node.OwnerDocument.CreateElement('Neighbours');
1762 Node.AppendChild(NewNode);
1763 for I := 0 to Neighbors.Count - 1 do begin
1764 NewNode2 := NewNode.OwnerDocument.CreateElement('Neighbour');
1765 NewNode.AppendChild(NewNode2);
1766 WriteInteger(NewNode2, 'Id', TCell(Neighbors[I]).Id);
1767 end;
1768 NewNode := Node.OwnerDocument.CreateElement('Polygon');
1769 Node.AppendChild(NewNode);
1770 for I := 0 to Length(Polygon) - 1 do begin
1771 NewNode2 := NewNode.OwnerDocument.CreateElement('Point');
1772 NewNode.AppendChild(NewNode2);
1773 WriteInteger(NewNode2, 'X', Polygon[I].X);
1774 WriteInteger(NewNode2, 'Y', Polygon[I].Y);
1775 end;
1776end;
1777
1778procedure TCell.Assign(Source: TCell);
1779begin
1780 Id := Source.Id;
1781 PosPx := Source.PosPx;
1782 Terrain := Source.Terrain;
1783 Polygon := Source.Polygon;
1784 Player := Source.Player;
1785 Mark := Source.Mark;
1786 // TODO: How to copy neighbours and moves list
1787end;
1788
1789function TCell.IsVisible(View: TView): Boolean;
1790var
1791 RectA, RectB: TRect;
1792begin
1793 RectA := GetPolygonRect(Polygon);
1794 RectB := View.SourceRect;
1795 Result := ((RectA.Left < RectB.Right) and (RectA.Right > RectB.Left) and
1796 (RectA.Top < RectB.Bottom) and (RectA.Bottom > RectB.Top));
1797end;
1798
1799function TCell.GetColor: TColor;
1800begin
1801 if Assigned(Player) then Result := Player.Color
1802 else Result := clGray;
1803end;
1804
1805function TCell.GetAvialPower: Integer;
1806var
1807 I: Integer;
1808begin
1809 Result := Power;
1810 for I := 0 to MovesFrom.Count - 1 do
1811 Result := Result - TUnitMove(MovesFrom[I]).CountOnce;
1812 if Result < 0 then Result := 0;
1813end;
1814
1815function TCell.GetAttackPower: Integer;
1816var
1817 I: Integer;
1818begin
1819 Result := 0;
1820 for I := 0 to MovesTo.Count - 1 do
1821 Result := Result + TUnitMove(MovesTo[I]).CountOnce;
1822end;
1823
1824constructor TCell.Create;
1825begin
1826 Player := nil;
1827 Neighbors := TCells.Create;
1828 Neighbors.OwnsObjects := False;
1829 MovesFrom := TUnitMoves.Create;
1830 MovesFrom.OwnsObjects := False;
1831 MovesTo := TUnitMoves.Create;
1832 MovesTo.OwnsObjects := False;
1833 Links := TCellLinks.Create;
1834 Links.OwnsObjects := False;
1835end;
1836
1837destructor TCell.Destroy;
1838var
1839 I: Integer;
1840begin
1841 for I := MovesFrom.Count - 1 downto 0 do
1842 TUnitMove(MovesFrom[I]).Free;
1843 FreeAndNil(MovesFrom);
1844 for I := MovesTo.Count - 1 downto 0 do
1845 TUnitMove(MovesTo[I]).Free;
1846 FreeAndNil(MovesTo);
1847 for I := Links.Count - 1 downto 0 do
1848 TCellLink(Links[I]).Free;
1849 FreeAndNil(Links);
1850 for I := Neighbors.Count - 1 downto 0 do
1851 if TCell(Neighbors[I]).Neighbors.Remove(Self) = -1 then
1852 raise Exception.Create('Can''t remove cell from neighbour cell');
1853 FreeAndNil(Neighbors);
1854 inherited Destroy;
1855end;
1856
1857{ TPlayer }
1858
1859function TView.CanvasToCellPos(Pos: TPoint): TPoint;
1860begin
1861 Result := Point(Trunc(Pos.X / Zoom + SourceRect.Left),
1862 Trunc(Pos.Y / Zoom + SourceRect.Top));
1863end;
1864
1865function TView.CellToCanvasPos(Pos: TPoint): TPoint;
1866begin
1867 Result := Point(Trunc((Pos.X - SourceRect.Left) * Zoom),
1868 Trunc((Pos.Y - SourceRect.Top) * Zoom));
1869end;
1870
1871function TView.CanvasToCellRect(Pos: TRect): TRect;
1872begin
1873 Result.TopLeft := CanvasToCellPos(Pos.TopLeft);
1874 Result.BottomRight := CanvasToCellPos(Pos.BottomRight);
1875end;
1876
1877function TView.CellToCanvasRect(Pos: TRect): TRect;
1878begin
1879 Result.TopLeft := CellToCanvasPos(Pos.TopLeft);
1880 Result.BottomRight := CellToCanvasPos(Pos.BottomRight);
1881end;
1882
1883procedure TView.Assign(Source: TView);
1884begin
1885 SourceRect := Source.SourceRect;
1886 FDestRect := Source.DestRect;
1887 FZoom := Source.Zoom;
1888 SelectedCell := Source.SelectedCell;
1889 FocusedCell := Source.FocusedCell;
1890end;
1891
1892procedure TPlayer.SetGame(AValue: TGame);
1893begin
1894 if FGame = AValue then Exit;
1895 FGame := AValue;
1896end;
1897
1898procedure TPlayer.Clear;
1899begin
1900 TurnStats.Clear;
1901end;
1902
1903procedure TPlayer.SetClient(AValue: TClient);
1904begin
1905 if FClient=AValue then Exit;
1906 if Assigned(FClient) then FClient.FControlPlayer := nil;
1907 FClient := AValue;
1908 if Assigned(FClient) then FClient.FControlPlayer := Self;
1909end;
1910
1911procedure TPlayer.LoadFromNode(Node: TDOMNode);
1912var
1913 NewNode: TDOMNode;
1914begin
1915 Id := ReadInteger(Node, 'Id', 0);
1916 Name := ReadString(Node, 'Name', '');
1917 Color := ReadInteger(Node, 'Color', clSilver);
1918 Mode := TPlayerMode(ReadInteger(Node, 'Mode', Integer(pmHuman)));
1919 StartCell := FGame.Map.Cells.FindById(ReadInteger(Node, 'StartCell', 0));
1920 StartUnits := ReadInteger(Node, 'StartUnits', 0);
1921 Agressivity := TComputerAgressivity(ReadInteger(Node, 'Agressivity', Integer(caMedium)));
1922 Defensive := ReadBoolean(Node, 'Defensive', False);
1923
1924 with Node do begin
1925 NewNode := FindNode('Map');
1926 if Assigned(NewNode) then
1927 PlayerMap.LoadFromNode(NewNode);
1928 end;
1929 with Node do begin
1930 NewNode := FindNode('TurnStats');
1931 if Assigned(NewNode) then
1932 TurnStats.LoadFromNode(NewNode);
1933 end;
1934end;
1935
1936procedure TPlayer.SaveToNode(Node: TDOMNode);
1937var
1938 NewNode: TDOMNode;
1939begin
1940 WriteInteger(Node, 'Id', Id);
1941 WriteString(Node, 'Name', Name);
1942 WriteInteger(Node, 'Color', Color);
1943 WriteInteger(Node, 'Mode', Integer(Mode));
1944 WriteInteger(Node, 'StartCell', StartCell.Id);
1945 WriteInteger(Node, 'StartUnits', StartUnits);
1946 WriteInteger(Node, 'Agressivity', Integer(Agressivity));
1947 WriteBoolean(Node, 'Defensive', Defensive);
1948
1949 with Node do begin
1950 NewNode := OwnerDocument.CreateElement('Map');
1951 AppendChild(NewNode);
1952 PlayerMap.SaveToNode(NewNode);
1953 end;
1954 with Node do begin
1955 NewNode := OwnerDocument.CreateElement('TurnStats');
1956 AppendChild(NewNode);
1957 TurnStats.SaveToNode(NewNode);
1958 end;
1959end;
1960
1961function CellCompare(Item1, Item2: Pointer): Integer;
1962begin
1963 if TCell(Item1).Power > TCell(Item2).Power then Result := 1
1964 else if TCell(Item1).Power < TCell(Item2).Power then Result := -1
1965 else Result := 0;
1966end;
1967
1968function CellCompareDescending(Item1, Item2: Pointer): Integer;
1969begin
1970 if TCell(Item1).Power > TCell(Item2).Power then Result := -1
1971 else if TCell(Item1).Power < TCell(Item2).Power then Result := 1
1972 else Result := 0;
1973end;
1974
1975procedure TComputer.AttackNeutral;
1976var
1977 AllCells: TCells;
1978 TotalPower: Integer;
1979 AttackPower: Integer;
1980 TotalAttackPower: Integer;
1981 I, J: Integer;
1982 C: Integer;
1983 CanAttack: Integer;
1984 TargetCells: TCells;
1985 S: string;
1986const
1987 AttackDiff = 1;
1988begin
1989 AllCells := Game.Map.Cells;
1990 TargetCells := TCells.Create;
1991 TargetCells.OwnsObjects := False;
1992
1993 // Get list of all attack target cells
1994 for C := 0 to AllCells.Count - 1 do
1995 with TCell(AllCells[C]) do begin
1996 if (Terrain <> ttVoid) and (Player = nil) then begin
1997 CanAttack := 0;
1998 for I := 0 to Neighbors.Count - 1 do
1999 if (TCell(Neighbors[I]).Player = Game.CurrentPlayer) then begin
2000 Inc(CanAttack);
2001 end;
2002 if CanAttack > 0 then TargetCells.Add(AllCells[C]);
2003 end;
2004 end;
2005
2006 // Sort ascending to attack cells with lower power first
2007 // Low power cells are better for expanding our teritorry
2008 TargetCells.Sort(CellCompare);
2009
2010 for C := 0 to TargetCells.Count - 1 do
2011 with TCell(TargetCells[C]) do begin
2012 // Attack to not owned cell yet
2013 // Count own possible power
2014 TotalPower := 0;
2015 for I := 0 to Neighbors.Count - 1 do
2016 if (TCell(Neighbors[I]).Player = Game.CurrentPlayer) then begin
2017 TotalPower := TotalPower + TCell(Neighbors[I]).GetAvialPower;
2018 end;
2019 // Attack if target is weaker
2020 if TotalPower >= (Power + AttackDiff) then begin
2021 TotalAttackPower := 0;
2022 for I := 0 to Neighbors.Count - 1 do
2023 if (TCell(Neighbors[I]).Player = Game.CurrentPlayer) then begin
2024 // Use only necessary power
2025 AttackPower := Power - TotalAttackPower + AttackDiff;
2026 if TCell(Neighbors[I]).GetAvialPower < AttackPower then
2027 AttackPower := TCell(Neighbors[I]).GetAvialPower;
2028 Game.SetMove(Tcell(Neighbors[I]), TCell(TargetCells[C]), AttackPower, False);
2029 TotalAttackPower := TotalAttackPower + AttackPower;
2030 end;
2031 end;
2032 end;
2033
2034 TargetCells.Free;
2035end;
2036
2037procedure TComputer.AttackPlayers;
2038var
2039 AllCells: TCells;
2040 TotalPower: Integer;
2041 AttackPower: Integer;
2042 TotalAttackPower: Integer;
2043 I, J: Integer;
2044 C: Integer;
2045 CanAttack: Integer;
2046 TargetCells: TCells;
2047 S: string;
2048const
2049 AttackDiff = 2;
2050begin
2051 if Game.CurrentPlayer.Defensive then Exit;
2052
2053 AllCells := Game.Map.Cells;
2054 TargetCells := TCells.Create;
2055 TargetCells.OwnsObjects := False;
2056
2057 // Get list of all attack target cells
2058 for C := 0 to AllCells.Count - 1 do
2059 with TCell(AllCells[C]) do begin
2060 if (Terrain <> ttVoid) and (Player <> Game.CurrentPlayer) and (Player <> nil) then begin
2061 CanAttack := 0;
2062 for I := 0 to Neighbors.Count - 1 do
2063 if (TCell(Neighbors[I]).Player = Game.CurrentPlayer) then begin
2064 Inc(CanAttack);
2065 end;
2066 if CanAttack > 0 then TargetCells.Add(AllCells[C]);
2067 end;
2068 end;
2069
2070 // Sort descending to attack cells with higher power first
2071 // Higher power enemy cells can grow faster and is more dangerous
2072 TargetCells.Sort(CellCompareDescending);
2073
2074 for C := 0 to TargetCells.Count - 1 do
2075 with TCell(TargetCells[C]) do begin
2076 // Attack to not owned cell yet
2077 // Count own possible power
2078 TotalPower := 0;
2079 for I := 0 to Neighbors.Count - 1 do
2080 if (TCell(Neighbors[I]).Player = Game.CurrentPlayer) then begin
2081 TotalPower := TotalPower + TCell(Neighbors[I]).GetAvialPower;
2082 end;
2083 // Attack if target is weaker
2084 if Game.AttackProbability(TotalPower, Power) >= ComputerAggroProbability[Game.CurrentPlayer.Agressivity] then begin
2085 // Try to limit total attacking power to necessary minimum
2086 while Game.AttackProbability(TotalPower - 1, Power) >= ComputerAggroProbability[Game.CurrentPlayer.Agressivity] do
2087 Dec(TotalPower);
2088
2089 // Collect required attack units from our cells
2090 TotalAttackPower := 0;
2091 for I := 0 to Neighbors.Count - 1 do
2092 if (TCell(Neighbors[I]).Player = Game.CurrentPlayer) then begin
2093 // Use only necessary power
2094 AttackPower := TotalPower - TotalAttackPower;
2095 if TCell(Neighbors[I]).GetAvialPower < AttackPower then
2096 AttackPower := TCell(Neighbors[I]).GetAvialPower;
2097 Game.SetMove(TCell(Neighbors[I]), TCell(TargetCells[C]), AttackPower, False);
2098 TotalAttackPower := TotalAttackPower + AttackPower;
2099 if TotalAttackPower >= TotalPower then Break;
2100 end;
2101 end;
2102 end;
2103
2104 TargetCells.Free;
2105end;
2106
2107procedure TComputer.InnerMoves;
2108var
2109 AllCells: TCells;
2110 I, J: Integer;
2111 C: Integer;
2112 CanAttack: Integer;
2113 TargetCells: TCells;
2114 NewTargetCells: TCells;
2115 Cells2: TCells;
2116 MovedPower: Integer;
2117begin
2118 // We need to move available power to borders to be available for attacks
2119 // or defense
2120 AllCells := Game.Map.Cells;
2121 TargetCells := TCells.Create;
2122 TargetCells.OwnsObjects := False;
2123 NewTargetCells := TCells.Create;
2124 NewTargetCells.OwnsObjects := False;
2125
2126 // Get list of all enemy border cells
2127 for C := 0 to AllCells.Count - 1 do
2128 with TCell(AllCells[C]) do begin
2129 if (Player <> Game.CurrentPlayer) and (Player <> nil) and (Terrain <> ttVoid) then begin
2130 CanAttack := 0;
2131 for I := 0 to Neighbors.Count - 1 do
2132 if ((TCell(Neighbors[I]).Player = Game.CurrentPlayer) or
2133 (TCell(Neighbors[I]).Player = nil)) and (TCell(Neighbors[I]).Terrain <> ttVoid) then begin
2134 Inc(CanAttack);
2135 end;
2136 if CanAttack > 0 then TargetCells.Add(AllCells[C]);
2137 end;
2138 end;
2139
2140 if CellProcessDirection then begin
2141 // Reverse array
2142 for I := 0 to (TargetCells.Count div 2) - 1 do
2143 TargetCells.Exchange(I, TargetCells.Count - 1 - I);
2144 end;
2145
2146 Game.Map.Cells.ClearMark;
2147
2148 while TargetCells.Count > 0 do begin
2149 // Set mark for selected border cells
2150 for C := 0 to TargetCells.Count - 1 do
2151 TCell(TargetCells[C]).Mark := True;
2152
2153 // Move all power from unmarked cells and mark them
2154 NewTargetCells.Count := 0;
2155 for C := 0 to TargetCells.Count - 1 do
2156 with TCell(TargetCells[C]) do begin
2157 for I := 0 to Neighbors.Count - 1 do begin
2158 if (TCell(Neighbors[I]).Terrain <> ttVoid) and (not TCell(Neighbors[I]).Mark) then begin
2159 if (TCell(TargetCells[C]).Player = Game.CurrentPlayer) and
2160 (TCell(Neighbors[I]).Player = Game.CurrentPlayer) then begin
2161 // Do not take units from front line
2162 Cells2 := TCell(Neighbors[I]).Neighbors;
2163 CanAttack := 0;
2164 for J := 0 to Cells2.Count - 1 do
2165 if ((TCell(Cells2[J]).Player <> Game.CurrentPlayer) or (TCell(Cells2[J]).Player = nil))
2166 and (TCell(Cells2[J]).Terrain <> ttVoid) then begin
2167 Inc(CanAttack);
2168 end;
2169 if CanAttack = 0 then begin
2170 MovedPower := TCell(Neighbors[I]).GetAvialPower;
2171 if (TCell(TargetCells[C]).GetAvialPower + TCell(TargetCells[C]).GetAttackPower + MovedPower) > Game.Map.MaxPower then
2172 MovedPower := Game.Map.MaxPower - TCell(TargetCells[C]).GetAvialPower - TCell(TargetCells[C]).GetAttackPower;
2173 Game.SetMove(TCell(Neighbors[I]), TCell(TargetCells[C]), MovedPower, False);
2174 end;
2175 end;
2176 TCell(Neighbors[I]).Mark := True;
2177 NewTargetCells.Add(TCell(Neighbors[I]));
2178 end;
2179 end;
2180 end;
2181
2182 // Use source cells NewTargetCells as new TargetCells
2183 TargetCells.Free;
2184 TargetCells := NewTargetCells;
2185 NewTargetCells := TCells.Create;
2186 NewTargetCells.OwnsObjects := False;
2187 end;
2188
2189 TargetCells.Free;
2190 NewTargetCells.Free;
2191end;
2192
2193procedure TComputer.IncreaseMoves;
2194var
2195 I: Integer;
2196begin
2197 // If available power remains then use all for existed unit moves
2198 for I := 0 to Game.Moves.Count - 1 do
2199 with TUnitMove(Game.Moves[I]) do begin
2200 if CellFrom.GetAvialPower > 0 then
2201 CountOnce := CountOnce + CellFrom.GetAvialPower;
2202 end;
2203end;
2204
2205procedure TComputer.Process;
2206begin
2207 AttackPlayers;
2208 AttackNeutral;
2209 InnerMoves;
2210 IncreaseMoves;
2211 //FallBack;
2212 CellProcessDirection := not CellProcessDirection;
2213end;
2214
2215procedure TComputer.FallBack;
2216var
2217 C: Integer;
2218 I: Integer;
2219 AllCells: TCells;
2220 BorderCells: TCells;
2221 EnemyPower: Integer;
2222begin
2223 BorderCells := TCells.Create;
2224 BorderCells.OwnsObjects := False;
2225 AllCells := Game.Map.Cells;
2226
2227 // Get list of border cells
2228 for C := 0 to AllCells.Count - 1 do
2229 with TCell(AllCells[C]) do begin
2230 if (Terrain <> ttVoid) and (Player = Game.CurrentPlayer) then begin
2231 if AttackersCount(TCell(AllCells[C])) > 0 then
2232 BorderCells.Add(AllCells[C]);
2233 end;
2234 end;
2235
2236 // Move all units back to inner area from weak border cells
2237 for C := 0 to BorderCells.Count - 1 do
2238 with TCell(BorderCells[C]) do begin
2239 // Calculate enemy power
2240 // TODO: Do not sum different enemy power to one value
2241 EnemyPower := 0;
2242 for I := 0 to Neighbors.Count - 1 do
2243 if (TCell(Neighbors[I]).Player <> Game.CurrentPlayer) and (TCell(Neighbors[I]).Player <> nil) then begin
2244 Inc(EnemyPower, TCell(Neighbors[I]).Power);
2245 end;
2246 if EnemyPower > (GetAvialPower + GetAttackPower) then begin
2247 // Fallback
2248 for I := MovesTo.Count - 1 downto 0 do
2249 TUnitMove(MovesTo[I]).Free;
2250 for I := 0 to Neighbors.Count - 1 do
2251 if (TCell(Neighbors[I]).Player = Game.CurrentPlayer) and (AttackersCount(TCell(Neighbors[I])) = 0) then begin
2252
2253 Game.SetMove(TCell(BorderCells[C]), TCell(Neighbors[I]), GetAvialPower, False);
2254 Break;
2255 end;
2256 end;
2257 end;
2258
2259 BorderCells.Free;
2260end;
2261
2262function TComputer.AttackersCount(Cell: TCell): Integer;
2263var
2264 I: Integer;
2265begin
2266 Result := 0;
2267 for I := 0 to Cell.Neighbors.Count - 1 do
2268 if (TCell(Cell.Neighbors[I]).Player <> Game.CurrentPlayer) and
2269 (TCell(Cell.Neighbors[I]).Player <> nil) then begin
2270 Inc(Result);
2271 end;
2272end;
2273
2274procedure TView.SelectCell(Pos: TPoint; Player: TPlayer; ShiftState: TShiftState);
2275var
2276 NewSelectedCell: TCell;
2277 UnitMove: TUnitMove;
2278 I: Integer;
2279begin
2280 NewSelectedCell := Game.Map.PosToCell(CanvasToCellPos(Pos), Self);
2281 if Assigned(NewSelectedCell) then begin
2282 if Assigned(SelectedCell) and Game.Map.IsCellsNeighbor(NewSelectedCell, SelectedCell) then begin
2283 if ssShift in ShiftState then begin
2284 // Make maximum unit move without confirmation dialog
2285 for I := SelectedCell.MovesFrom.Count - 1 downto 0 do
2286 TUnitMove(SelectedCell.MovesFrom[I]).Free;
2287 Game.SetMove(SelectedCell, NewSelectedCell, SelectedCell.Power, False);
2288 SelectedCell := nil;
2289 end else
2290 if ssCtrl in ShiftState then begin
2291 // If CTRL key pressed then storno all moved from selected cell and
2292 // move all power to new selected cell
2293 for I := SelectedCell.MovesFrom.Count - 1 downto 0 do
2294 TUnitMove(SelectedCell.MovesFrom[I]).Free;
2295 UnitMove := Game.SetMove(SelectedCell, NewSelectedCell, SelectedCell.Power, False);
2296 if Assigned(UnitMove) then
2297 UnitMove.CountRepeat := Player.Game.Map.MaxPower;
2298 if NewSelectedCell.Player = Player then SelectedCell := NewSelectedCell
2299 else SelectedCell := nil;
2300 end else begin
2301 Game.SetMove(SelectedCell, NewSelectedCell, SelectedCell.Power);
2302 SelectedCell := nil;
2303 end;
2304 end else
2305 if (NewSelectedCell <> SelectedCell) and (NewSelectedCell.Player = Player) then
2306 SelectedCell := NewSelectedCell
2307 else
2308 if (NewSelectedCell = SelectedCell) and (NewSelectedCell.Player = Player) then
2309 SelectedCell := nil;
2310 end;
2311end;
2312
2313procedure TView.CenterMap;
2314var
2315 MapRect: TRect;
2316begin
2317 MapRect := Game.Map.GetPixelRect;
2318 SourceRect := Bounds(MapRect.Left + (MapRect.Right - MapRect.Left) div 2 - (SourceRect.Right - SourceRect.Left) div 2,
2319 MapRect.Top + (MapRect.Bottom - MapRect.Top) div 2 - (SourceRect.Bottom - SourceRect.Top) div 2,
2320 SourceRect.Right - SourceRect.Left,
2321 SourceRect.Bottom - SourceRect.Top);
2322end;
2323
2324procedure TView.CenterPlayerCity(Player: TPlayer);
2325begin
2326 SourceRect := Bounds(Player.StartCell.PosPx.X - (SourceRect.Right - SourceRect.Left) div 2,
2327 Player.StartCell.PosPx.Y - (SourceRect.Bottom - SourceRect.Top) div 2,
2328 SourceRect.Right - SourceRect.Left,
2329 SourceRect.Bottom - SourceRect.Top);
2330end;
2331
2332procedure TPlayer.Paint(Canvas: TCanvas; View: TView);
2333begin
2334 PlayerMap.Paint(Canvas, View);
2335end;
2336
2337constructor TPlayer.Create;
2338begin
2339 StartUnits := DefaultPlayerStartUnits;
2340 StartCell := nil;
2341 PlayerMap := TPlayerMap.Create;
2342 PlayerMap.Player := Self;
2343 TurnStats := TGameTurnStats.Create;
2344end;
2345
2346destructor TPlayer.Destroy;
2347begin
2348 FreeAndNil(TurnStats);
2349 FreeAndNil(PlayerMap);
2350 inherited Destroy;
2351end;
2352
2353procedure TPlayer.Assign(Source: TPlayer);
2354begin
2355 Id := Source.Id;
2356 Name := Source.Name;
2357 Color := Source.Color;
2358 Mode := Source.Mode;
2359 Game := Source.Game;
2360 TotalCells := Source.TotalCells;
2361 TotalUnits := Source.TotalUnits;
2362 StartUnits := Source.StartUnits;
2363 StartCell := Source.StartCell;
2364 Agressivity := Source.Agressivity;
2365 Defensive := Source.Defensive;
2366end;
2367
2368{ TGame }
2369
2370function ComparePointer(Item1, Item2: Pointer): Integer;
2371begin
2372 Result := -CompareValue(Integer(Item1), Integer(Item2));
2373end;
2374
2375procedure TGame.Attack(var AttackPower, DefendPower: Integer);
2376var
2377 AttackerDiceCount: Integer;
2378 DefenderDiceCount: Integer;
2379 S: string;
2380 I: Integer;
2381 AttackRolls: TList;
2382 DefendRolls: TList;
2383begin
2384 AttackRolls := TList.Create;
2385 DefendRolls := TList.Create;
2386 if AttackPower < 1 then
2387 raise Exception.Create('Attacker power have to be higher then 0.');
2388 if DefendPower < 0 then
2389 raise Exception.Create('Defender power have to be higher then or equal to 0.');
2390 while (AttackPower > 0) and (DefendPower > 0) do begin
2391 // Risk game rules:
2392 // Each side do their dice roll and compare result. Defender wins tie.
2393 // Attacker can use three dices and defender two
2394 AttackerDiceCount := Min(AttackPower, 3);
2395 DefenderDiceCount := Min(DefendPower, 2);
2396 // Roll and sort numbers
2397 AttackRolls.Count := AttackerDiceCount;
2398 for I := 0 to AttackerDiceCount - 1 do begin
2399 AttackRolls[I] := Pointer(Random(7));
2400 end;
2401 AttackRolls.Sort(ComparePointer);
2402 S := 'Att:';
2403 for I := 0 to AttackerDiceCount - 1 do
2404 S := S + IntToStr(Integer(AttackRolls[I])) + ', ';
2405 DefendRolls.Count := DefenderDiceCount;
2406 for I := 0 to DefenderDiceCount - 1 do begin
2407 DefendRolls[I] := Pointer(Random(7));
2408 end;
2409 DefendRolls.Sort(ComparePointer);
2410 S := S + ' Def:';
2411 for I := 0 to DefenderDiceCount - 1 do
2412 S := S + IntToStr(Integer(DefendRolls[I])) + ', ';
2413 // Resolution
2414 for I := 0 to Min(AttackerDiceCount, DefenderDiceCount) - 1 do
2415 if AttackRolls[I] > DefendRolls[I] then Dec(DefendPower)
2416 else Dec(AttackPower);
2417 end;
2418 AttackRolls.Free;
2419 DefendRolls.Free;
2420end;
2421
2422function TGame.AttackProbability(AttackCount, DefendCount: Integer): Double;
2423var
2424 OA, OD: Integer;
2425 Len: Integer;
2426 I: Integer;
2427begin
2428 if AttackCount = 0 then begin
2429 Result := 0;
2430 Exit;
2431 end;
2432 if DefendCount = 0 then begin
2433 Result := 1;
2434 Exit;
2435 end;
2436
2437 // Enlarge probability cache table on demand
2438 if Length(ProbabilityMatrix) < AttackCount then begin
2439 SetLength(ProbabilityMatrix, AttackCount);
2440 end;
2441 if Length(ProbabilityMatrix[AttackCount - 1]) < DefendCount then begin
2442 Len := Length(ProbabilityMatrix[AttackCount - 1]);
2443 SetLength(ProbabilityMatrix[AttackCount - 1], DefendCount);
2444 for I := Len to Length(ProbabilityMatrix[AttackCount - 1]) - 1 do
2445 ProbabilityMatrix[AttackCount - 1][I] := -1;
2446 end;
2447
2448 if ProbabilityMatrix[AttackCount - 1, DefendCount - 1] <> -1 then begin
2449 // Use cached value
2450 Result := ProbabilityMatrix[AttackCount - 1, DefendCount - 1];
2451 Exit;
2452 end else Result := 1;
2453
2454 OA := Min(AttackCount, 3);
2455 OD := Min(DefendCount, 2);
2456
2457 if (OA = 1) and (OD = 1) then
2458 Result := 0.4167 * AttackProbability(AttackCount, DefendCount - 1) +
2459 0.5833 * AttackProbability(AttackCount - 1, DefendCount)
2460 else if (OA = 2) and (OD = 1) then
2461 Result := 0.5787 * AttackProbability(AttackCount, DefendCount - 1) +
2462 0.4213 * AttackProbability(AttackCount - 1, DefendCount)
2463 else if (OA = 3) and (OD = 1) then
2464 Result := 0.6597 * AttackProbability(AttackCount, DefendCount - 1) +
2465 0.3403 * AttackProbability(AttackCount - 1, DefendCount)
2466 else if (OA = 1) and (OD = 2) then
2467 Result := 0.2546 * AttackProbability(AttackCount, DefendCount - 1) +
2468 0.7454 * AttackProbability(AttackCount - 1, DefendCount)
2469 else if (OA = 2) and (OD = 2) then
2470 Result := 0.2276 * AttackProbability(AttackCount, DefendCount - 2) +
2471 0.4483 * AttackProbability(AttackCount - 2, DefendCount) +
2472 0.3241 * AttackProbability(AttackCount - 1, DefendCount - 1)
2473 else if (OA = 3) and (OD = 2) then
2474 Result := 0.3717 * AttackProbability(AttackCount, DefendCount - 2) +
2475 0.2926 * AttackProbability(AttackCount - 2, DefendCount) +
2476 0.3358 * AttackProbability(AttackCount - 1, DefendCount - 1);
2477 ProbabilityMatrix[AttackCount - 1, DefendCount - 1] := Result;
2478end;
2479
2480procedure TGame.MoveAll(Player: TPlayer);
2481var
2482 I: Integer;
2483 AttackerPower: Integer;
2484 DefenderPower: Integer;
2485 UnitCount: Integer;
2486begin
2487 I := 0;
2488 while I < Moves.Count do
2489 with TUnitMove(Moves[I]) do begin
2490 if CountOnce > 0 then begin
2491 if CellFrom.Player = Player then begin
2492 UnitCount := CountOnce;
2493 if CountOnce > CellFrom.Power then
2494 UnitCount := CellFrom.Power;
2495 if CellTo.Player = Player then begin
2496 // Inner move
2497 CellTo.Power := CellTo.Power + UnitCount;
2498 end else begin
2499 AttackerPower := UnitCount;
2500 DefenderPower := CellTo.Power;
2501 Attack(AttackerPower, DefenderPower);
2502 if DefenderPower = 0 then begin
2503 // Attacker wins with possible loses
2504 ClearMovesFromCell(CellTo);
2505 CellTo.Player := Player;
2506 CellTo.Power := AttackerPower;
2507 end else
2508 if AttackerPower = 0 then begin
2509 // Defender wins with possible loses
2510 CellTo.Power := DefenderPower;
2511 end else
2512 raise Exception.Create(SUnfinishedBattle);
2513 end;
2514 CellFrom.Power := CellFrom.Power - UnitCount;
2515 CountOnce := 0;
2516 end;
2517 end;
2518 Inc(I);
2519 end;
2520 // Remove empty moves
2521 for I := Moves.Count - 1 downto 0 do
2522 if (TUnitMove(Moves[I]).CellFrom.Player = Player) and
2523 (TUnitMove(Moves[I]).CountOnce = 0) and (TUnitMove(Moves[I]).CountRepeat = 0) then
2524 Moves.Delete(I);
2525end;
2526
2527procedure TGame.ClearMovesFromCell(Cell: TCell);
2528var
2529 I: Integer;
2530begin
2531 for I := Moves.Count - 1 downto 0 do
2532 if TUnitMove(Moves[I]).CellFrom = Cell then
2533 Moves.Delete(I);
2534end;
2535
2536procedure TGame.SetMapType(AValue: TMapType);
2537var
2538 OldMap: TMap;
2539begin
2540 if FMapType = AValue then Exit;
2541 OldMap := Map;
2542 case AValue of
2543 mtNone: Map := TMap.Create;
2544 mtHexagon: Map := THexMap.Create;
2545 mtSquare: Map := TSquareMap.Create;
2546 mtTriangle: Map := TTriangleMap.Create;
2547 mtVoronoi: Map := TVoronoiMap.Create;
2548 else Map := TMap.Create;
2549 end;
2550 Map.Assign(OldMap);
2551 OldMap.Free;
2552 FMapType := AValue;
2553end;
2554
2555function TGame.SetMove(CellFrom, CellTo: TCell; Power: Integer; Confirmation: Boolean = True): TUnitMove;
2556var
2557 NewMove: TUnitMove;
2558 OldMove: TUnitMove;
2559 I: Integer;
2560 CountOnce: Integer;
2561 CountRepeat: Integer;
2562 Confirm: Boolean;
2563begin
2564 I := 0;
2565 Confirm := True;
2566 while (I < Moves.Count) and ((TUnitMove(Moves[I]).CellFrom <> CellFrom) or
2567 (TUnitMove(Moves[I]).CellTo <> CellTo)) do Inc(I);
2568 if I < Moves.Count then OldMove := TUnitMove(Moves[I])
2569 else OldMove := nil;
2570 Result := OldMove;
2571 if Assigned(OldMove) then begin
2572 CountOnce := OldMove.CountOnce;
2573 CountRepeat := OldMove.CountRepeat;
2574 if Assigned(CurrentPlayer) and Confirmation and
2575 Assigned(FOnMove) then FOnMove(CellFrom, CellTo, CountOnce, CountRepeat, True, Confirm);
2576 end else begin
2577 CountOnce := Power;
2578 CountRepeat := 0;
2579 if Assigned(CurrentPlayer) and Confirmation and
2580 Assigned(FOnMove) then FOnMove(CellFrom, CellTo, CountOnce, CountRepeat, False, Confirm);
2581 end;
2582 if Confirm then begin
2583 if Assigned(OldMove) then begin
2584 // Already have such move
2585 if (CountOnce = 0) and (CountRepeat = 0) then Moves.Delete(I)
2586 else begin
2587 OldMove.CountOnce := CountOnce;
2588 OldMove.CountRepeat := CountRepeat;
2589 CheckCounterMove(OldMove);
2590 end;
2591 end else begin
2592 // Add new move
2593 if (CountOnce > 0) or (CountRepeat > 0) then begin
2594 NewMove := TUnitMove(Moves[Moves.Add(TUnitMove.Create)]);
2595 NewMove.List := Moves;
2596 NewMove.CellFrom := CellFrom;
2597 NewMove.CellTo := CellTo;
2598 NewMove.CountOnce := CountOnce;
2599 NewMove.CountRepeat := CountRepeat;
2600 Result := NewMove;
2601 CheckCounterMove(NewMove);
2602 end;
2603 end;
2604 end;
2605end;
2606
2607procedure TGame.SetRunning(AValue: Boolean);
2608var
2609 I: Integer;
2610begin
2611 if FRunning = AValue then Exit;
2612 if AValue then begin
2613 if Players.Count < 2 then raise Exception.Create(SMinimumPlayers);
2614 FRunning := AValue;
2615 end else begin
2616 FRunning := AValue;
2617 for I := 0 to Clients.Count - 1 do
2618 with TClient(Clients[I]) do begin
2619 View.Clear;
2620 end;
2621 end;
2622end;
2623
2624procedure TGame.UpdateRepeatMoves(Player: TPlayer);
2625var
2626 I: Integer;
2627begin
2628 for I := 0 to Moves.Count - 1 do
2629 with TUnitMove(Moves[I]) do begin
2630 if CellFrom.Player = Player then
2631 if CountRepeat <= CellFrom.GetAvialPower then
2632 CountOnce := CountRepeat
2633 else CountOnce := CellFrom.GetAvialPower;
2634 end;
2635end;
2636
2637procedure TGame.CheckCounterMove(Move: TUnitMove);
2638var
2639 I: Integer;
2640 CounterMove: TUnitMove;
2641begin
2642 I := 0;
2643 while (I < Moves.Count) and ((TUnitMove(Moves[I]).CellTo <> Move.CellFrom) or
2644 (TUnitMove(Moves[I]).CellFrom <> Move.CellTo)) do Inc(I);
2645 if I < Moves.Count then CounterMove := TUnitMove(Moves[I])
2646 else CounterMove := nil;
2647 if Assigned(CounterMove) then begin
2648 // For now, just remove counter move
2649 Moves.Remove(CounterMove);
2650 end;
2651end;
2652
2653function TGame.SearchDifferentCellArea(List: TCells; SourceArea, DestArea: TMapArea): TCell;
2654var
2655 NewList: TCells;
2656 NewListVoid: TCells;
2657 I: Integer;
2658 C: Integer;
2659begin
2660 Result := nil;
2661 NewList := TCells.Create;
2662 NewList.OwnsObjects := False;
2663 NewListVoid := TCells.Create;
2664 NewListVoid.OwnsObjects := False;
2665
2666 for C := 0 to List.Count - 1 do
2667 with TCell(List[C]) do begin
2668 for I := 0 to Neighbors.Count - 1 do
2669 with TCell(Neighbors[I]) do
2670 if (not Mark) and (Terrain <> ttVoid) and (Area <> SourceArea) and ((DestArea = nil) or (DestArea = Area)) then begin
2671 NewList.Add(TCell(TCell(List[C]).Neighbors[I]));
2672 Mark := True;
2673 end else
2674 if (not Mark) and (Terrain = ttVoid) then begin
2675 NewListVoid.Add(TCell(TCell(List[C]).Neighbors[I]));
2676 Mark := True;
2677 end;
2678 end;
2679
2680 if NewList.Count > 0 then begin
2681 // We found cell with different area
2682 Result := TCell(NewList[Random(NewList.Count)]);
2683 end else
2684 if NewListVoid.Count > 0 then begin
2685 // Cell was not found but we have more void cells to check
2686 Result := SearchDifferentCellArea(NewListVoid, SourceArea, DestArea);
2687 end;
2688
2689 NewListVoid.Free;
2690 NewList.Free;
2691end;
2692
2693procedure TGame.BuildTerrain;
2694var
2695 C: Integer;
2696begin
2697 if (Map.Shape = msImage) and FileExists(MapImageFileName) and
2698 (LoadedImageFileName <> MapImageFileName) then begin
2699 LoadedImageFileName := MapImageFileName;
2700 Map.Image.Picture.LoadFromFile(MapImageFileName);
2701 end;
2702
2703 // Randomize map terrain
2704 for C := 0 to Map.Cells.Count - 1 do
2705 with TCell(Map.Cells[C]) do begin
2706 if (VoidEnabled and (Random < VoidPercentage / 100)) or
2707 (Map.IsOutsideShape(PosPx)) then Terrain := ttVoid
2708 else begin
2709 if CityEnabled and (Random < CityPercentage / 100) then Terrain := ttCity
2710 else Terrain := ttNormal;
2711 end;
2712 Power := Random(MaxNeutralUnits + 1);
2713 Player := nil;
2714 end;
2715end;
2716
2717procedure TGame.BuildBridges;
2718var
2719 List: TCells;
2720 BorderList: TCells;
2721 Cell: TCell;
2722 FoundCell1: TCell;
2723 FoundCell2: TCell;
2724 I: Integer;
2725 J: Integer;
2726 NewLink: TCellLink;
2727begin
2728 List := TCells.Create;
2729 List.OwnsObjects := False;
2730
2731 BorderList := TCells.Create;
2732 BorderList.OwnsObjects := False;
2733
2734 // Build area bridges
2735 if Map.Areas.Count > 1 then
2736 for I := 0 to Map.Areas.Count - 1 do
2737 with TMapArea(Map.Areas[I]) do begin
2738 GetBorderCells(BorderList);
2739 for J := 0 to 1 do begin
2740
2741 Cell := TCell(BorderList[Random(BorderList.Count)]);
2742 List.Clear;
2743 List.Add(Cell);
2744
2745 Map.Cells.ClearMark;
2746
2747 // Find nearest cell with different area
2748 FoundCell1 := SearchDifferentCellArea(List, TMapArea(Map.Areas[I]), nil);
2749 if Assigned(FoundCell1) then begin
2750 // Again find back nearest cell with different area.
2751 // This will ensure that both cells are closest ones
2752
2753 Map.Cells.ClearMark;
2754 List[0] := FoundCell1;
2755 FoundCell2 := SearchDifferentCellArea(List, FoundCell1.Area, TMapArea(Map.Areas[I]));
2756 if Assigned(FoundCell2) then begin
2757 // Check if link not exists already
2758 if not Assigned(FoundCell1.Links.FindByCells(FoundCell1, FoundCell2)) then begin
2759 NewLink := TCellLink.Create;
2760 FoundCell1.Neighbors.Add(FoundCell2);
2761 FoundCell1.Links.Add(NewLink);
2762 FoundCell2.Neighbors.Add(FoundCell1);
2763 FoundCell2.Links.Add(NewLink);
2764 SetLength(NewLink.Points, 2);
2765 NewLink.Cells.Add(FoundCell1);
2766 NewLink.Points[0] := FoundCell1.PosPx;
2767 NewLink.Cells.Add(FoundCell2);
2768 NewLink.Points[1] := FoundCell2.PosPx;
2769 NewLink.Map := Map;
2770 Map.CellLinks.Add(NewLink);
2771 Inc(BridgeCount);
2772 end;
2773 end;
2774 end;
2775 end;
2776
2777 end;
2778 List.Free;
2779 BorderList.Free;
2780end;
2781
2782procedure TGame.BuildMapAreas;
2783var
2784 C: Integer;
2785 NewArea: TMapArea;
2786begin
2787 for C := 0 to Map.Cells.Count - 1 do
2788 with TCell(Map.Cells[C]) do
2789 Area := nil;
2790 Map.Areas.Clear;
2791 for C := 0 to Map.Cells.Count - 1 do
2792 with TCell(Map.Cells[C]) do
2793 if (Terrain <> ttVoid) and (not Assigned(Area)) then begin
2794 NewArea := TMapArea(Map.Areas[Map.Areas.Add(TMapArea.Create)]);
2795 NewArea.Id := Map.Areas.Count;
2796 NewArea.Map := Map;
2797 Area := NewArea;
2798 AreaExtend;
2799 end;
2800end;
2801
2802procedure TGame.InitClients;
2803var
2804 I: Integer;
2805begin
2806 Clients.Clear;
2807 Clients.New('Spectator');
2808 for I := 0 to Players.Count - 1 do
2809 with TPlayer(Players[I]) do
2810 if Mode = pmHuman then begin
2811 Clients.New(TPlayer(Players[I]).Name);
2812 TPlayer(Players[I]).Client := TClient(Clients.Last);
2813 end;
2814
2815 for I := 0 to Clients.Count - 1 do
2816 with TClient(Clients[I]) do begin
2817 View.Clear;
2818 View.Zoom := 1;
2819 if Assigned(ControlPlayer) then View.CenterPlayerCity(ControlPlayer)
2820 else View.CenterMap;
2821 end;
2822end;
2823
2824procedure TGame.SaveConfig(Config: TXmlConfig; Path: string);
2825begin
2826 with Config do begin
2827 SetValue(Path + '/GridType', Integer(MapType));
2828 SetValue(Path + '/MapImage', MapImageFileName);
2829 SetValue(Path + '/SymetricMap', SymetricMap);
2830 SetValue(Path + '/FogOfWar', FogOfWar);
2831 SetValue(Path + '/VoidEnabled', VoidEnabled);
2832 SetValue(Path + '/VoidPercentage', VoidPercentage);
2833 SetValue(Path + '/MapSizeX', Map.Size.X);
2834 SetValue(Path + '/MapSizeY', Map.Size.Y);
2835 SetValue(Path + '/MapShape', Integer(Map.Shape));
2836 SetValue(Path + '/CityEnabled', CityEnabled);
2837 SetValue(Path + '/CityPercentage', CityPercentage);
2838 SetValue(Path + '/BridgeEnabled', BridgeEnabled);
2839 SetValue(Path + '/GrowAmount', Integer(GrowAmount));
2840 SetValue(Path + '/GrowCells', Integer(GrowCells));
2841 SetValue(Path + '/WinObjective', Integer(WinObjective));
2842 end;
2843end;
2844
2845procedure TGame.LoadConfig(Config: TXmlConfig; Path: string);
2846var
2847 Value: Integer;
2848begin
2849 with Config do begin
2850 MapType := TMapType(GetValue(Path + '/GridType', Integer(mtHexagon)));
2851 MapImageFileName := GetValue(Path + '/MapImage', MapImageFileName);
2852 SymetricMap := GetValue(Path + '/SymetricMap', False);
2853 FogOfWar := GetValue(Path + '/FogOfWar', False);
2854 VoidEnabled := GetValue(Path + '/VoidEnabled', True);
2855 VoidPercentage := GetValue(Path + '/VoidPercentage', 20);
2856 Map.Size := Point(GetValue(Path + '/MapSizeX', 10),
2857 GetValue(Path + '/MapSizeY', 10));
2858 Value := GetValue(Path + '/MapShape', 0);
2859 if (Value >= Integer(Low(TMapShape))) and (Value <= Integer(High(TMapShape))) then
2860 Map.Shape := TMapShape(Value) else Map.Shape := Low(TMapShape);
2861 CityEnabled := GetValue(Path + '/CityEnabled', False);
2862 CityPercentage := GetValue(Path + '/CityPercentage', 10);
2863 BridgeEnabled := GetValue(Path + '/BridgeEnabled', True);
2864 Value := GetValue(Path + '/GrowAmount', Integer(gaBySquareRoot));
2865 if (Value >= Integer(Low(TGrowAmount))) and (Value <= Integer(High(TGrowAmount))) then
2866 GrowAmount := TGrowAmount(Value) else GrowAmount := Low(TGrowAmount);
2867 Value := GetValue(Path + '/GrowCells', Integer(gcPlayerAll));
2868 if (Value >= Integer(Low(TGrowCells))) and (Value <= Integer(High(TGrowCells))) then
2869 GrowCells := TGrowCells(Value) else GrowCells := Low(TGrowCells);
2870 Value := GetValue(Path + '/WinObjective', Integer(woDefeatAllOponents));
2871 if (Value >= Integer(Low(TWinObjective))) and (Value <= Integer(High(TWinObjective))) then
2872 WinObjective := TWinObjective(Value) else WinObjective := Low(TWinObjective);
2873 end;
2874end;
2875
2876procedure TGame.LoadFromFile(FileName: string);
2877var
2878 NewNode: TDOMNode;
2879 Doc: TXMLDocument;
2880 RootNode: TDOMNode;
2881 I: Integer;
2882begin
2883 Self.FileName := FileName;
2884 ReadXMLFile(Doc, UTF8Decode(FileName));
2885 with Doc do try
2886 if Doc.DocumentElement.NodeName <> 'XtacticsGame' then
2887 raise Exception.Create(SWrongFileFormat);
2888 RootNode := Doc.DocumentElement;
2889 with RootNode do begin
2890 SymetricMap := ReadBoolean(RootNode, 'SymetricMap', False);
2891 FogOfWar := ReadBoolean(RootNode, 'FogOfWar', False);
2892 VoidEnabled := ReadBoolean(RootNode, 'VoidEnabled', False);
2893 VoidPercentage := ReadInteger(RootNode, 'VoidPercentage', 0);
2894 MaxNeutralUnits := ReadInteger(RootNode, 'MaxNeutralUnits', 3);
2895 GrowCells := TGrowCells(ReadInteger(RootNode, 'GrowCells', Integer(gcNone)));
2896 GrowAmount := TGrowAmount(ReadInteger(RootNode, 'GrowAmount', Integer(gaByOne)));
2897 CityEnabled := ReadBoolean(RootNode, 'CityEnabled', False);
2898 CityPercentage := ReadInteger(RootNode, 'CityPercentage', 0);
2899 BridgeEnabled := ReadBoolean(RootNode, 'BridgeEnabled', False);
2900 TurnCounter := ReadInteger(RootNode, 'TurnCounter', 0);
2901 WinObjective := TWinObjective(ReadInteger(RootNode, 'WinObjective', Integer(woDefeatAllOponents)));
2902 ReadInteger(RootNode, 'StayAliveForDefinedTurns', StayAliveForDefinedTurns);
2903
2904 NewNode := FindNode('Map');
2905 if Assigned(NewNode) then
2906 Map.LoadFromNode(NewNode);
2907
2908 NewNode := FindNode('Players');
2909 if Assigned(NewNode) then
2910 Players.LoadFromNode(NewNode);
2911 if Players.Count > 0 then CurrentPlayer := TPlayer(Players[0])
2912 else CurrentPlayer := nil;
2913
2914 InitClients;
2915
2916 NewNode := FindNode('UnitMoves');
2917 if Assigned(NewNode) then
2918 Moves.LoadFromNode(NewNode);
2919
2920 Map.Cells.FixRefId;
2921
2922 for I := 0 to Players.Count - 1 do begin
2923 TPlayer(Players[I]).PlayerMap.Update;
2924 TPlayer(Players[I]).PlayerMap.CheckVisibility;
2925 end;
2926 Running := ReadBoolean(RootNode, 'Running', True);
2927 end;
2928 finally
2929 Doc.Free;
2930 end;
2931end;
2932
2933procedure TGame.SaveToFile(FileName: string);
2934var
2935 NewNode: TDOMNode;
2936 Doc: TXMLDocument;
2937 RootNode: TDOMNode;
2938begin
2939 Self.FileName := FileName;
2940 Doc := TXMLDocument.Create;
2941 with Doc do try
2942 RootNode := CreateElement('XtacticsGame');
2943 AppendChild(RootNode);
2944 with RootNode do begin
2945 WriteBoolean(RootNode, 'SymetricMap', SymetricMap);
2946 WriteBoolean(RootNode, 'FogOfWar', FogOfWar);
2947 WriteBoolean(RootNode, 'VoidEnabled', VoidEnabled);
2948 WriteInteger(RootNode, 'VoidPercentage', VoidPercentage);
2949 WriteInteger(RootNode, 'MaxNeutralUnits', MaxNeutralUnits);
2950 WriteInteger(RootNode, 'GrowCells', Integer(GrowCells));
2951 WriteInteger(RootNode, 'GrowAmount', Integer(GrowAmount));
2952 WriteBoolean(RootNode, 'CityEnabled', CityEnabled);
2953 WriteInteger(RootNode, 'CityPercentage', CityPercentage);
2954 WriteBoolean(RootNode, 'BridgeEnabled', BridgeEnabled);
2955 WriteInteger(RootNode, 'TurnCounter', TurnCounter);
2956 WriteInteger(RootNode, 'WinObjective', Integer(WinObjective));
2957 WriteInteger(RootNode, 'StayAliveForDefinedTurns', StayAliveForDefinedTurns);
2958 WriteBoolean(RootNode, 'Running', Running);
2959
2960 NewNode := OwnerDocument.CreateElement('Map');
2961 AppendChild(NewNode);
2962 Map.SaveToNode(NewNode);
2963
2964 NewNode := OwnerDocument.CreateElement('Players');
2965 AppendChild(NewNode);
2966 Players.SaveToNode(NewNode);
2967
2968 NewNode := OwnerDocument.CreateElement('UnitMoves');
2969 AppendChild(NewNode);
2970 Moves.SaveToNode(NewNode);
2971 end;
2972 ForceDirectoriesUTF8(ExtractFileDir(FileName));
2973 WriteXMLFile(Doc, UTF8Decode(FileName));
2974 finally
2975 Doc.Free;
2976 end;
2977end;
2978
2979procedure TGame.ComputePlayerStats;
2980var
2981 I: Integer;
2982 J: Integer;
2983begin
2984 for I := 0 to Players.Count - 1 do
2985 with TPlayer(Players[I]) do begin
2986 TotalUnits := 0;
2987 TotalCells := 0;
2988 TotalCities := 0;
2989
2990 TotalDiscovered := 0;
2991 for J := 0 to PlayerMap.Cells.Count - 1 do
2992 with TPlayerCell(PlayerMap.Cells[J]) do begin
2993 if Explored then Inc(TotalDiscovered);
2994 end;
2995 end;
2996
2997 Map.ComputePlayerStats;
2998end;
2999
3000procedure TGame.RecordTurnStats;
3001var
3002 I: Integer;
3003 NewStat: TGameTurnStat;
3004begin
3005 for I := 0 to Players.Count - 1 do
3006 with TPlayer(Players[I]) do begin
3007 NewStat := TGameTurnStat.Create;
3008 NewStat.DiscoveredCells := TotalDiscovered;
3009 NewStat.OccupiedCells := TotalCells;
3010 NewStat.Units := TotalUnits;
3011 NewStat.Cities := TotalCities;
3012 TurnStats.Add(NewStat);
3013 end;
3014end;
3015
3016function TGame.GetAlivePlayers: TPlayerArray;
3017var
3018 I: Integer;
3019begin
3020 SetLength(Result, 0);
3021 for I := 0 to Players.Count - 1 do
3022 if TPlayer(Players[I]).TotalCells > 0 then begin
3023 SetLength(Result, Length(Result) + 1);
3024 Result[Length(Result) - 1] := TPlayer(Players[I]);
3025 end;
3026end;
3027
3028function TGame.GetAlivePlayersWithCities: TPlayerArray;
3029var
3030 I: Integer;
3031begin
3032 SetLength(Result, 0);
3033 for I := 0 to Players.Count - 1 do
3034 if TPlayer(Players[I]).TotalCities > 0 then begin
3035 SetLength(Result, Length(Result) + 1);
3036 Result[Length(Result) - 1] := TPlayer(Players[I]);
3037 end;
3038end;
3039
3040procedure TGame.NextTurn;
3041var
3042 PrevPlayer: TPlayer;
3043begin
3044 //TODO CurrentPlayer.View.SelectedCell := nil;
3045 MoveAll(CurrentPlayer);
3046 Map.Grow(CurrentPlayer);
3047 UpdateRepeatMoves(CurrentPlayer);
3048 ComputePlayerStats;
3049 PrevPlayer := CurrentPlayer;
3050 // Skip dead players
3051 repeat
3052 CurrentPlayer := TPlayer(Players[(Players.IndexOf(CurrentPlayer) + 1) mod Players.Count]);
3053 if Assigned(FOnPlayerChange) then
3054 FOnPlayerChange(Self);
3055 until CurrentPlayer.TotalCells > 0;
3056 if Players.IndexOf(CurrentPlayer) < Players.IndexOf(PrevPlayer) then begin
3057 Inc(TurnCounter);
3058 RecordTurnStats;
3059 if Assigned(FOnNewTurn) then
3060 FOnNewTurn(Self);
3061 end;
3062 CheckWinObjective;
3063 CurrentPlayer.PlayerMap.CheckVisibility;
3064 // For computers take view from previous human
3065 //if CurrentPlayer.Mode = pmComputer then CurrentPlayer.View.Assign(PrevPlayer.View);
3066end;
3067
3068procedure TGame.CheckWinObjective;
3069var
3070 AlivePlayers: TPlayerArray;
3071 Winner: TPlayer;
3072begin
3073 Winner := nil;
3074 if WinObjective = woDefeatAllOponents then begin
3075 AlivePlayers := GetAlivePlayers;
3076 if (Length(AlivePlayers) <= 1) then begin
3077 if Length(AlivePlayers) > 0 then Winner := TPlayer(AlivePlayers[0]);
3078 EndGame(Winner);
3079 end;
3080 end else
3081 if WinObjective = woDefeatAllOponentsCities then begin
3082 AlivePlayers := GetAlivePlayersWithCities;
3083 if (Length(AlivePlayers) <= 1) then begin
3084 if Length(AlivePlayers) > 0 then Winner := TPlayer(AlivePlayers[0]);
3085 EndGame(Winner);
3086 end;
3087 end else
3088 if WinObjective = woSpecialCaptureCell then begin
3089 if Assigned(SpecialCaptureCell) and Assigned(SpecialCaptureCell.Player) then
3090 EndGame(SpecialCaptureCell.Player);
3091 end else
3092 if WinObjective = woStayAliveForDefinedTurns then begin
3093 // TODO: Not only one can win but multiple human players can survive.
3094 if TurnCounter > StayAliveForDefinedTurns then
3095 EndGame(nil);
3096 end;
3097end;
3098
3099constructor TGame.Create;
3100begin
3101 Moves := TUnitMoves.Create;
3102 Moves.Game := Self;
3103 Map := TMap.Create;
3104 Players := TPlayers.Create;
3105 Players.Game := Self;
3106 Clients := TClients.Create;
3107 Clients.Game := Self;
3108
3109 MapImageFileName := 'Images/Maps/WorldMap.png';
3110 Randomize;
3111
3112 Players.New(SPlayer + ' 1', clBlue, pmHuman);
3113 Players.New(SPlayer + ' 2', clRed, pmComputer);
3114
3115 VoidEnabled := True;
3116 VoidPercentage := 20;
3117 MaxNeutralUnits := 4;
3118
3119 Map.Game := Self;
3120 Map.Size := Point(3, 3);
3121end;
3122
3123destructor TGame.Destroy;
3124begin
3125 FreeAndNil(Clients);
3126 FreeAndNil(Moves);
3127 FreeAndNil(Players);
3128 FreeAndNil(Map);
3129 inherited Destroy;
3130end;
3131
3132procedure TGame.New;
3133var
3134 I: Integer;
3135 Counter: Integer;
3136 C: Integer;
3137 LastAreaCount: Integer;
3138begin
3139 FileName := SNewGameFile;
3140 TurnCounter := 1;
3141 Moves.Clear;
3142
3143 BuildTerrain;
3144
3145 // Build bridges
3146 Map.CellLinks.Clear;
3147 if BridgeEnabled then begin
3148 BuildMapAreas;
3149 LastAreaCount := -1;
3150 while (Map.Areas.Count > 1) and (Map.Areas.Count <> LastAreaCount) do begin
3151 LastAreaCount := Map.Areas.Count;
3152 BuildBridges;
3153 BuildMapAreas;
3154 end;
3155 end;
3156
3157 if SymetricMap then begin
3158 for C := 0 to (Map.Cells.Count div 2) - 1 do begin
3159 TCell(Map.Cells[C]).Terrain :=
3160 TCell(Map.Cells[Map.Cells.Count - 1 - C]).Terrain;
3161 TCell(Map.Cells[C]).Power :=
3162 TCell(Map.Cells[Map.Cells.Count - 1 - C]).Power;
3163 end;
3164 end;
3165
3166 for I := 0 to Players.Count - 1 do
3167 with TPlayer(Players[I]) do begin
3168 Clear;
3169 PlayerMap.Update;
3170 if (Map.Size.X > 0) and (Map.Size.Y > 0) then begin
3171 // Try to obtain start cell for each player
3172 StartCell := nil;
3173 Counter := 0;
3174 while not Assigned(StartCell) or Assigned(StartCell.Player) or
3175 (StartCell.Terrain = ttVoid) do begin
3176 StartCell := TCell(Map.Cells[Random(Map.Cells.Count)]);
3177 Inc(Counter);
3178 if Counter > 100 then
3179 raise Exception.Create(SCannotSetPlayerStartCells);
3180 end;
3181 if SymetricMap and (I = 1) then
3182 StartCell := TCell(Map.Cells[Map.Cells.Count - 1 - Map.Cells.IndexOf(TPlayer(Players[0]).StartCell)]);
3183
3184 StartCell.Terrain := ttCity;
3185 StartCell.Player := TPlayer(Players[I]);
3186 StartCell.Power := TPlayer(Players[I]).StartUnits;
3187 end;
3188 PlayerMap.CheckVisibility;
3189 end;
3190 if Players.Count > 0 then CurrentPlayer := TPlayer(Players[0])
3191 else CurrentPlayer := nil;
3192
3193 InitClients;
3194end;
3195
3196procedure TGame.EndGame(Winner: TPlayer = nil);
3197begin
3198 Running := False;
3199 if Assigned(OnWin) and Assigned(Winner) then OnWin(Winner);
3200end;
3201
3202procedure TMap.Paint(Canvas: TCanvas; View: TView);
3203var
3204 I: Integer;
3205 C: Integer;
3206 Cell: TCell;
3207 PosFrom, PosTo: TPoint;
3208 Angle: Double;
3209 ArrowCenter: TPoint;
3210 Move: TUnitMove;
3211begin
3212 with Canvas, View do
3213 try
3214 Lock;
3215
3216 // Draw cell links
3217 Pen.Color := clBlack;
3218 Pen.Style := psSolid;
3219 Pen.Width := 3;
3220 for C := 0 to CellLinks.Count - 1 do
3221 with TCellLink(CellLinks[C]) do begin
3222 if Length(Points) >= 2 then begin
3223 MoveTo(View.CellToCanvasPos(Points[0]));
3224 for I := 1 to Length(Points) - 1 do
3225 LineTo(View.CellToCanvasPos(Points[I]));
3226 end;
3227 end;
3228
3229 // Draw cells
3230 for C := 0 to Cells.Count - 1 do begin
3231 Cell := TCell(Cells[C]);
3232 if (Cell.Terrain <> ttVoid) and Cell.IsVisible(View) then begin
3233 if Assigned(SelectedCell) and (SelectedCell = Cell) then
3234 Brush.Color := clGreen
3235 else if Assigned(SelectedCell) and IsCellsNeighbor(SelectedCell, Cell) then
3236 Brush.Color := clPurple
3237 else Brush.Color := Cell.GetColor;
3238 //Pen.Color := clBlack;
3239 PaintCell(Canvas, Cell.PosPx, IntToStr(Cell.GetAvialPower), View, Cell);
3240 end;
3241
3242 end;
3243
3244 // Draw arrows
3245 Pen.Color := clCream;
3246 for I := 0 to Game.Moves.Count - 1 do begin
3247 Move := TUnitMove(Game.Moves[I]);
3248 PosFrom := CellToPos(Move.CellFrom);
3249 PosTo := CellToPos(Move.CellTo);
3250 if Move.CountRepeat > 0 then Pen.Width := 2
3251 else Pen.Width := 1;
3252 Angle := ArcTan((PosTo.Y - PosFrom.Y) / (PosTo.X - PosFrom.X));
3253 if Sign(PosTo.X - PosFrom.X) = -1 then Angle := Angle + Pi;
3254 ArrowCenter := View.CellToCanvasPos(Point(Trunc(PosFrom.X + (PosTo.X - PosFrom.X) / 2),
3255 Trunc(PosFrom.Y + (PosTo.Y - PosFrom.Y) / 2)));
3256 DrawArrow(Canvas, View, ArrowCenter,
3257 Angle, IntToStr(Move.CountOnce));
3258 end;
3259 finally
3260 Unlock;
3261 end;
3262end;
3263
3264end.
Note: See TracBrowser for help on using the repository browser.