source: trunk/Player.pas

Last change on this file was 342, checked in by chronos, 5 hours ago
  • Added: More tests.
File size: 35.6 KB
Line 
1unit Player;
2
3interface
4
5uses
6 Classes, SysUtils, Graphics, Map, DOM, Generics.Collections, Generics.Defaults,
7 XMLConf, XML, Math, Geometry, Units, Nation, ItemList, TurnStats;
8
9type
10 TUnitMoves = class;
11 TPlayerCells = class;
12 TPlayerMap = class;
13 TPlayer = class;
14
15 { TPlayerCell }
16
17 TPlayerCell = class
18 MovesFrom: TUnitMoves;
19 MovesTo: TUnitMoves;
20 Explored: Boolean;
21 InVisibleRange: Boolean;
22 MapCell: TCell;
23 List: TPlayerCells;
24 Neighbors: TPlayerCells;
25 procedure ConnectTo(Cell: TPlayerCell);
26 procedure DisconnectFrom(Cell: TPlayerCell);
27 function GetAvialPower: Integer;
28 function GetAvialPowerForMove: Integer;
29 function GetAttackPower: Integer;
30 procedure LoadFromNode(Node: TDOMNode);
31 procedure SaveToNode(Node: TDOMNode);
32 constructor Create;
33 destructor Destroy; override;
34 end;
35
36 { TPlayerCells }
37
38 TPlayerCells = class(TObjectList<TPlayerCell>)
39 Map: TPlayerMap;
40 function FindByCellId(Id: Integer): TPlayerCell;
41 function SearchCell(Cell: TCell): TPlayerCell;
42 procedure LoadFromNode(Node: TDOMNode);
43 procedure SaveToNode(Node: TDOMNode);
44 end;
45
46 { TPlayerMap }
47
48 TPlayerMap = class
49 Cells: TPlayerCells;
50 Player: TPlayer;
51 function PosToCell(Pos: TPoint): TPlayerCell; virtual;
52 function CellToPos(Cell: TPlayerCell): TPoint; virtual;
53 procedure LoadFromNode(Node: TDOMNode);
54 procedure SaveToNode(Node: TDOMNode);
55 procedure Update;
56 procedure Clear;
57 constructor Create;
58 destructor Destroy; override;
59 procedure CheckVisibility;
60 end;
61
62 TPlayerMode = (pmHuman, pmComputer);
63 TComputerAgressivity = (caLow, caMedium, caHigh);
64 TUnitMove = class;
65
66 TMoveEvent = procedure(CellFrom, CellTo: TPlayerCell; var CountOnce, CountRepeat: Integer;
67 Update: Boolean; var Confirm: Boolean) of object;
68 TMoveUpdatedEvent = procedure(UnitMove: TUnitMove) of object;
69
70 { TPlayer }
71
72 TPlayer = class(TItem)
73 private
74 FGame: TObject; //TGame;
75 FMode: TPlayerMode;
76 FOnMove: TMoveEvent;
77 procedure MoveUnit(UnitMove: TUnitMove);
78 procedure SetGame(AValue: TObject); // TGame
79 procedure Attack(var AttackPower, DefendPower: Integer);
80 procedure ClearMovesFromCell(Cell: TPlayerCell);
81 procedure CheckCounterMove(Move: TUnitMove);
82 procedure SetMode(AValue: TPlayerMode);
83 public
84 Color: TColor;
85 TotalUnits: Integer;
86 TotalCells: Integer;
87 TotalCities: Integer;
88 TotalDiscovered: Integer;
89 TotalWinObjectiveCells: Integer;
90 StartUnits: Integer;
91 StartCell: TCell;
92 PlayerMap: TPlayerMap;
93 Defensive: Boolean;
94 Agressivity: TComputerAgressivity;
95 TurnStats: TGameTurnStats;
96 Moves: TUnitMoves;
97 Units: TUnits;
98 Nation: TNation;
99 class function GetFields: TItemFields; override;
100 procedure GetValue(Index: Integer; out Value); override;
101 procedure SetValue(Index: Integer; var Value); override;
102 function GetReferenceList(Index: Integer): TItemList; override;
103 class function GetClassSysName: string; override;
104 class function GetClassName: string; override;
105 function IsAllowedMoveTarget(CellFrom, CellTo: TPlayerCell): Boolean;
106 procedure ReduceMovesPower;
107 procedure RemoveInvalidMoves;
108 procedure RemoveEmptyUnits;
109 procedure InitUnitMoves;
110 procedure UpdateRepeatMoves;
111 procedure UpdateEmptyCellsNeutral;
112 procedure MoveAll;
113 procedure Grow;
114 function SetMove(CellFrom, CellTo: TPlayerCell; Power: Integer; Confirmation: Boolean = True): TUnitMove;
115 procedure Reset;
116 procedure Surrender;
117 function IsAlive: Boolean;
118 procedure Clear;
119 procedure LoadFromNode(Node: TDOMNode); override;
120 procedure SaveToNode(Node: TDOMNode); override;
121 constructor Create; override;
122 destructor Destroy; override;
123 procedure Assign(Source: TItem); override;
124 procedure LoadConfig(Config: TXmlConfig; Path: string);
125 procedure SaveConfig(Config: TXmlConfig; Path: string);
126 property Game: TObject read FGame write SetGame; // TGame
127 property Mode: TPlayerMode read FMode write SetMode;
128 property OnMove: TMoveEvent read FOnMove write FOnMove;
129 end;
130
131 TPlayerEvent = procedure (Player: TPlayer) of object;
132 TPlayerArray = array of TPlayer;
133
134 { TPlayers }
135
136 TPlayers = class(TItemList)
137 public
138 Game: TObject; //TGame;
139 class function GetItemClass: TItemClass; override;
140 procedure New(Name: string; Color: TColor; Mode: TPlayerMode);
141 function CreateItem(Name: string = ''): TItem; override;
142 function GetFirstHuman: TPlayer;
143 procedure LoadConfig(Config: TXmlConfig; Path: string);
144 procedure SaveConfig(Config: TXmlConfig; Path: string);
145 function GetAliveCount: Integer;
146 procedure GetAlivePlayers(Players: TPlayers); overload;
147 function GetAlivePlayers: TPlayerArray; overload;
148 function GetAlivePlayersWithCities: TPlayerArray;
149 end;
150
151 { TUnitMove }
152
153 TUnitMove = class
154 private
155 FCellFrom: TPlayerCell;
156 FCellTo: TPlayerCell;
157 procedure SetCellFrom(AValue: TPlayerCell);
158 procedure SetCellTo(AValue: TPlayerCell);
159 public
160 List: TUnitMoves;
161 CountOnce: Integer;
162 CountRepeat: Integer;
163 procedure LoadFromNode(Node: TDOMNode);
164 procedure SaveToNode(Node: TDOMNode);
165 constructor Create;
166 destructor Destroy; override;
167 property CellFrom: TPlayerCell read FCellFrom write SetCellFrom;
168 property CellTo: TPlayerCell read FCellTo write SetCellTo;
169 end;
170
171 { TUnitMoves }
172
173 TUnitMoves = class(TObjectList<TUnitMove>)
174 Game: TObject; //TGame;
175 Player: TPlayer;
176 function SearchByFromTo(CellFrom, CellTo: TPlayerCell): TUnitMove;
177 procedure LoadFromNode(Node: TDOMNode);
178 procedure SaveToNode(Node: TDOMNode);
179 end;
180
181function CellCompare(constref Item1, Item2: TPlayerCell): Integer;
182function CellCompareDescending(constref Item1, Item2: TPlayerCell): Integer;
183
184
185implementation
186
187uses
188 Game, GameSystem, Building, UnitKind;
189
190resourcestring
191 SAttackerPowerPositive = 'Attacker power have to be higher then 0.';
192 SDefenderPowerPositive = 'Defender power have to be higher then or equal to 0.';
193 SUnfinishedBattle = 'Unfinished battle';
194 SLow = 'Low';
195 SMedium = 'Medium';
196 SHigh = 'High';
197 SHuman = 'Human';
198 SComputer = 'Computer';
199 SNation = 'Nation';
200 SMode = 'Mode';
201 SColor = 'Color';
202 SAgressivity = 'Agressivity';
203 SStartUnits = 'Start units';
204 SDefensive = 'Defensive';
205
206function ComparePointer(constref Item1, Item2: Integer): Integer;
207begin
208 Result := -CompareValue(Item1, Item2);
209end;
210
211
212{ TPlayerCell }
213
214procedure TPlayerCell.LoadFromNode(Node: TDOMNode);
215var
216 C: TCell;
217 CellId: Integer;
218begin
219 Explored := ReadBoolean(Node, 'Explored', False);
220 CellId := ReadInteger(Node, 'MapCell', 0);
221 C := TGame(List.Map.Player.Game).Map.Cells.FindById(CellId);
222 if not Assigned(C) then
223 raise Exception.Create('Cell map not found ' + IntToStr(CellId));
224 if List.SearchCell(C) <> nil then
225 raise Exception.Create('Map cell used twice ' + IntToStr(CellId));
226 MapCell := C;
227end;
228
229procedure TPlayerCell.ConnectTo(Cell: TPlayerCell);
230begin
231 if Cell = Self then
232 raise Exception.Create('Can''t connect player cell to itself');
233 if (Cell.Neighbors.IndexOf(Self) = -1) and
234 (Neighbors.IndexOf(Cell) = -1) then begin
235 Cell.Neighbors.Add(Self);
236 Neighbors.Add(Cell);
237 end;
238end;
239
240procedure TPlayerCell.DisconnectFrom(Cell: TPlayerCell);
241var
242 I: Integer;
243begin
244 I := Cell.Neighbors.IndexOf(Self);
245 if I >= 0 then Cell.Neighbors.Delete(I) else
246 raise Exception.Create('Can''t disconnect neigboring cells.');
247 I := Neighbors.IndexOf(Cell);
248 if I >= 0 then Neighbors.Delete(I)
249 else Exception.Create('Can''t disconnect neigboring cells.');
250end;
251
252function TPlayerCell.GetAvialPower: Integer;
253var
254 UnitMove: TUnitMove;
255begin
256 Result := 0;
257 if Assigned(MapCell.OneUnit) then
258 Result := MapCell.OneUnit.Power;
259 for UnitMove in MovesFrom do
260 Result := Result - UnitMove.CountOnce;
261end;
262
263function TPlayerCell.GetAvialPowerForMove: Integer;
264var
265 UnitMove: TUnitMove;
266begin
267 Result := 0;
268 if Assigned(MapCell.OneUnit) then
269 if MapCell.OneUnit.Moves > 0 then Result := MapCell.OneUnit.Power;
270 for UnitMove in MovesFrom do
271 Result := Result - UnitMove.CountOnce;
272end;
273
274function TPlayerCell.GetAttackPower: Integer;
275var
276 I: Integer;
277begin
278 Result := 0;
279 for I := 0 to MovesTo.Count - 1 do
280 Result := Result + TUnitMove(MovesTo[I]).CountOnce;
281end;
282
283procedure TPlayerCell.SaveToNode(Node: TDOMNode);
284begin
285 WriteBoolean(Node, 'Explored', Explored);
286 WriteInteger(Node, 'MapCell', MapCell.Id);
287end;
288
289constructor TPlayerCell.Create;
290begin
291 MovesFrom := TUnitMoves.Create;
292 MovesFrom.OwnsObjects := False;
293 MovesTo := TUnitMoves.Create;
294 MovesTo.OwnsObjects := False;
295 Neighbors := TPlayerCells.Create;
296 Neighbors.OwnsObjects := False;
297end;
298
299destructor TPlayerCell.Destroy;
300var
301 I: Integer;
302begin
303 for I := MovesFrom.Count - 1 downto 0 do
304 if TUnitMove(MovesFrom[I]).List.Remove(TUnitMove(MovesFrom[I])) = -1 then
305 raise Exception.Create('MoveFrom cell remove error');
306 FreeAndNil(MovesFrom);
307 for I := MovesTo.Count - 1 downto 0 do
308 if TUnitMove(MovesTo[I]).List.Remove(TUnitMove(MovesTo[I])) = -1 then
309 raise Exception.Create('MoveTo cell remove error');
310 FreeAndNil(MovesTo);
311 for I := Neighbors.Count - 1 downto 0 do
312 if Neighbors[I].Neighbors.Remove(Self) = -1 then
313 raise Exception.Create(SCellRemoveNeighborError);
314 FreeAndNil(Neighbors);
315 inherited;
316end;
317
318{ TPlayerCells }
319
320function TPlayerCells.FindByCellId(Id: Integer): TPlayerCell;
321var
322 I: Integer;
323begin
324 I := 0;
325 while (I < Count) and (Items[I].MapCell.Id <> Id) do Inc(I);
326 if I < Count then Result := Items[I]
327 else Result := nil;
328end;
329
330function TPlayerCells.SearchCell(Cell: TCell): TPlayerCell;
331var
332 I: Integer;
333begin
334 I := 0;
335 while (I < Count) and (Items[I].MapCell <> Cell) do Inc(I);
336 if I < Count then Result := Items[I]
337 else Result := nil;
338end;
339
340procedure TPlayerCells.LoadFromNode(Node: TDOMNode);
341var
342 Node2: TDOMNode;
343 NewCell: TPlayerCell;
344begin
345 Count := 0;
346 Node2 := Node.FirstChild;
347 while Assigned(Node2) and (Node2.NodeName = 'Cell') do begin
348 NewCell := TPlayerCell.Create;
349 NewCell.List := Self;
350 NewCell.LoadFromNode(Node2);
351 Add(NewCell);
352 Node2 := Node2.NextSibling;
353 end;
354end;
355
356procedure TPlayerCells.SaveToNode(Node: TDOMNode);
357var
358 I: Integer;
359 NewNode: TDOMNode;
360begin
361 for I := 0 to Count - 1 do begin;
362 NewNode := Node.OwnerDocument.CreateElement('Cell');
363 Node.AppendChild(NewNode);
364 Items[I].SaveToNode(NewNode);
365 end;
366end;
367
368{ TPlayerMap }
369
370function TPlayerMap.PosToCell(Pos: TPoint): TPlayerCell;
371var
372 I: Integer;
373begin
374 // TODO: Can it be optimized somehow?
375 Result := nil;
376 for I := 0 to Cells.Count - 1 do
377 if Cells[I].MapCell.Terrain <> ttVoid then begin
378 if Cells[I].MapCell.Polygon.IsPointInside(Pos) then begin
379 Result := Cells[I];
380 Exit;
381 end;
382 end;
383end;
384
385function TPlayerMap.CellToPos(Cell: TPlayerCell): TPoint;
386begin
387 Result := Cell.MapCell.PosPx;
388end;
389
390procedure TPlayerMap.LoadFromNode(Node: TDOMNode);
391var
392 NewNode: TDOMNode;
393begin
394 with Node do begin
395 NewNode := FindNode('Cells');
396 if Assigned(NewNode) then
397 Cells.LoadFromNode(NewNode);
398 end;
399end;
400
401procedure TPlayerMap.SaveToNode(Node: TDOMNode);
402var
403 NewNode: TDOMNode;
404begin
405 with Node do begin
406 NewNode := OwnerDocument.CreateElement('Cells');
407 AppendChild(NewNode);
408 Cells.SaveToNode(NewNode);
409 end;
410end;
411
412procedure TPlayerMap.Update;
413var
414 I: Integer;
415 J: Integer;
416 OldCount: Integer;
417begin
418 for I := 0 to Cells.Count - 1 do
419 with TPlayerCell(Cells[I]) do begin
420 for J := Neighbors.Count - 1 downto 0 do
421 DisconnectFrom(Neighbors[J]);
422 end;
423
424 // Update players cells count to map cells count to be 1:1
425 OldCount := Cells.Count;
426 Cells.Count := TGame(Player.Game).Map.Cells.Count;
427 for I := OldCount to Cells.Count - 1 do
428 Cells[I] := TPlayerCell.Create;
429
430 for I := 0 to TGame(Player.Game).Map.Cells.Count - 1 do begin
431 with Cells[I] do begin
432 List := Cells;
433 Explored := False;
434 InVisibleRange := False;
435 MapCell := TGame(Player.Game).Map.Cells[I];
436 TGame(Player.Game).Map.Cells[I].PlayerCell := Cells[I];
437 end;
438 end;
439
440 for I := 0 to Cells.Count - 1 do
441 with TPlayerCell(Cells[I]) do begin
442 for J := 0 to MapCell.Neighbors.Count - 1 do
443 ConnectTo(TPlayerCell(TCell(MapCell.Neighbors[J]).PlayerCell));
444 end;
445end;
446
447procedure TPlayerMap.Clear;
448begin
449 Cells.Clear;
450end;
451
452constructor TPlayerMap.Create;
453begin
454 Cells := TPlayerCells.Create;
455 Cells.Map := Self;
456end;
457
458destructor TPlayerMap.Destroy;
459begin
460 FreeAndNil(Cells);
461 inherited;
462end;
463
464procedure TPlayerMap.CheckVisibility;
465var
466 I: Integer;
467 C: Integer;
468 NeighCount: Integer;
469begin
470 for I := 0 to Cells.Count - 1 do
471 with Cells[I] do begin
472 NeighCount := 0;
473 for C := 0 to MapCell.Neighbors.Count - 1 do
474 if MapCell.Neighbors[C].Player = Player then
475 Inc(NeighCount);
476
477 InVisibleRange := (NeighCount > 0) or (MapCell.Player = Player);
478 if InVisibleRange and not Explored then Explored := True;
479 end;
480end;
481
482{ TPlayers }
483
484function TPlayers.GetAliveCount: Integer;
485var
486 I: Integer;
487begin
488 Result := 0;
489 for I := 0 to Count - 1 do
490 if TPlayer(Items[I]).IsAlive then Inc(Result);
491end;
492
493procedure TPlayers.GetAlivePlayers(Players: TPlayers);
494var
495 I: Integer;
496begin
497 Players.Clear;
498 for I := 0 to Count - 1 do
499 if TPlayer(Items[I]).IsAlive then Players.Add(TPlayer(Items[I]));
500end;
501
502class function TPlayers.GetItemClass: TItemClass;
503begin
504 Result := TPlayer;
505end;
506
507procedure TPlayers.New(Name: string; Color: TColor; Mode: TPlayerMode);
508begin
509 AddItem(Name);
510 TPlayer(Last).Color := Color;
511 TPlayer(Last).Mode := Mode;
512 if Mode = pmComputer then
513 TPlayer(Last).Agressivity := caMedium;
514end;
515
516function TPlayers.CreateItem(Name: string): TItem;
517begin
518 Result := inherited;
519 TPlayer(Result).Game := Game;
520end;
521
522function TPlayers.GetFirstHuman: TPlayer;
523var
524 I: Integer;
525begin
526 I := 0;
527 while (I < Count) and (TPlayer(Items[I]).Mode <> pmHuman) do Inc(I);
528 if I < Count then Result := TPlayer(Items[I])
529 else Result := nil;
530end;
531
532procedure TPlayers.LoadConfig(Config: TXmlConfig; Path: string);
533var
534 I: Integer;
535 NewCount: Integer;
536begin
537 with Config do begin
538 NewCount := GetValue(DOMString(Path + '/Count'), -1);
539 NewId := 1;
540 if NewCount >= MinPlayerCount then begin
541 Self.Clear;
542 Count := NewCount;
543 for I := 0 to Count - 1 do begin
544 Items[I] := CreateItem;
545 Items[I].Id := GetNewId;
546 TPlayer(Items[I]).LoadConfig(Config, Path + '/Player' + IntToStr(I));
547 end;
548 end;
549 end;
550end;
551
552procedure TPlayers.SaveConfig(Config: TXmlConfig; Path: string);
553var
554 I: Integer;
555begin
556 for I := 0 to Count - 1 do
557 TPlayer(Items[I]).SaveConfig(Config, Path + '/Player' + IntToStr(I));
558 with Config do begin
559 SetValue(DOMString(Path + '/Count'), Count);
560 end;
561end;
562
563function TPlayers.GetAlivePlayers: TPlayerArray;
564var
565 I: Integer;
566begin
567 Result := Default(TPlayerArray);
568 SetLength(Result, 0);
569 for I := 0 to Count - 1 do
570 if TPlayer(Items[I]).IsAlive then begin
571 SetLength(Result, Length(Result) + 1);
572 Result[Length(Result) - 1] := TPlayer(Items[I]);
573 end;
574end;
575
576function TPlayers.GetAlivePlayersWithCities: TPlayerArray;
577var
578 I: Integer;
579begin
580 Result := Default(TPlayerArray);
581 SetLength(Result, 0);
582 for I := 0 to Count - 1 do
583 if TPlayer(Items[I]).TotalCities > 0 then begin
584 SetLength(Result, Length(Result) + 1);
585 Result[Length(Result) - 1] := TPlayer(Items[I]);
586 end;
587end;
588
589{ TUnitMoves }
590
591function TUnitMoves.SearchByFromTo(CellFrom, CellTo: TPlayerCell): TUnitMove;
592var
593 UnitMove: TUnitMove;
594begin
595 Result := nil;
596 for UnitMove in Self do
597 if (UnitMove.CellFrom = CellFrom) and (UnitMove.CellTo = CellTo) then begin
598 Result := UnitMove;
599 Break;
600 end;
601end;
602
603procedure TUnitMoves.LoadFromNode(Node: TDOMNode);
604var
605 Node2: TDOMNode;
606 NewUnitMove: TUnitMove;
607begin
608 Count := 0;
609 Node2 := Node.FirstChild;
610 while Assigned(Node2) and (Node2.NodeName = 'UnitMove') do begin
611 NewUnitMove := TUnitMove.Create;
612 NewUnitMove.List := Self;
613 NewUnitMove.LoadFromNode(Node2);
614 Add(NewUnitMove);
615 Node2 := Node2.NextSibling;
616 end;
617end;
618
619procedure TUnitMoves.SaveToNode(Node: TDOMNode);
620var
621 I: Integer;
622 NewNode: TDOMNode;
623begin
624 for I := 0 to Count - 1 do begin;
625 NewNode := Node.OwnerDocument.CreateElement('UnitMove');
626 Node.AppendChild(NewNode);
627 Items[I].SaveToNode(NewNode);
628 end;
629end;
630
631{ TUnitMove }
632
633procedure TUnitMove.SetCellFrom(AValue: TPlayerCell);
634var
635 OldValue: TPlayerCell;
636begin
637 if FCellFrom = AValue then Exit;
638 OldValue := FCellFrom;
639 FCellFrom := nil;
640 if Assigned(OldValue) then begin
641 if OldValue.MovesFrom.IndexOf(Self) <> -1 then
642 OldValue.MovesFrom.Remove(Self)
643 else raise Exception.Create('Unit move not found');
644 end;
645 FCellFrom := AValue;
646 if Assigned(FCellFrom) then begin
647 if FCellFrom.MovesFrom.IndexOf(Self) = -1 then
648 FCellFrom.MovesFrom.Add(Self)
649 else raise Exception.Create('Unit move already exists');
650 end else
651end;
652
653procedure TUnitMove.SetCellTo(AValue: TPlayerCell);
654var
655 OldValue: TPlayerCell;
656begin
657 if FCellTo = AValue then Exit;
658 OldValue := FCellTo;
659 FCellTo := nil;
660 if Assigned(OldValue) then begin
661 if OldValue.MovesTo.IndexOf(Self) <> -1 then
662 OldValue.MovesTo.Remove(Self)
663 else raise Exception.Create('Unit move not found');
664 end;
665 FCellTo := AValue;
666 if Assigned(FCellTo) then begin
667 if FCellTo.MovesTo.IndexOf(Self) = -1 then
668 FCellTo.MovesTo.Add(Self)
669 else raise Exception.Create('Unit move already exists');
670 end else
671end;
672
673procedure TUnitMove.LoadFromNode(Node: TDOMNode);
674begin
675 CountOnce := ReadInteger(Node, 'CountOnce', 0);
676 CountRepeat := ReadInteger(Node, 'CountRepeat', 0);
677 CellFrom := List.Player.PlayerMap.Cells.FindByCellId(ReadInteger(Node, 'CellFrom', 0));
678 CellTo := List.Player.PlayerMap.Cells.FindByCellId(ReadInteger(Node, 'CellTo', 0));
679end;
680
681procedure TUnitMove.SaveToNode(Node: TDOMNode);
682begin
683 WriteInteger(Node, 'CountOnce', CountOnce);
684 WriteInteger(Node, 'CountRepeat', CountRepeat);
685 WriteInteger(Node, 'CellFrom', CellFrom.MapCell.Id);
686 WriteInteger(Node, 'CellTo', CellTo.MapCell.Id);
687end;
688
689constructor TUnitMove.Create;
690begin
691 List := nil; // Is later set to parent list owning item
692 FCellFrom := nil;
693 FCellTo := nil;
694end;
695
696destructor TUnitMove.Destroy;
697begin
698 CellFrom := nil;
699 CellTo := nil;
700 List := nil;
701 inherited;
702end;
703
704{ TPlayer }
705
706procedure TPlayer.SetGame(AValue: TObject);
707begin
708 if FGame = AValue then Exit;
709 FGame := AValue;
710 Moves.Game := AValue;
711end;
712
713procedure TPlayer.Clear;
714begin
715 TurnStats.Clear;
716 Moves.Clear;
717 PlayerMap.Clear;
718end;
719
720{procedure TPlayer.SetClient(AValue: TClient);
721begin
722 if FClient = AValue then Exit;
723 if Assigned(FClient) then FClient.FControlPlayer := nil;
724 FClient := AValue;
725 if Assigned(FClient) then FClient.FControlPlayer := Self;
726end;
727}
728
729procedure TPlayer.LoadFromNode(Node: TDOMNode);
730var
731 NewNode: TDOMNode;
732begin
733 inherited;
734 StartCell := TGame(FGame).Map.Cells.FindById(ReadInteger(Node, 'StartCell', 0));
735
736 with Node do begin
737 NewNode := FindNode('Map');
738 if Assigned(NewNode) then
739 PlayerMap.LoadFromNode(NewNode);
740 PlayerMap.Update;
741 end;
742 with Node do begin
743 NewNode := FindNode('UnitMoves');
744 if Assigned(NewNode) then
745 Moves.LoadFromNode(NewNode);
746 end;
747 with Node do begin
748 NewNode := FindNode('TurnStats');
749 if Assigned(NewNode) then
750 TurnStats.LoadFromNode(NewNode);
751 end;
752end;
753
754procedure TPlayer.SaveToNode(Node: TDOMNode);
755var
756 NewNode: TDOMNode;
757begin
758 inherited;
759 if Assigned(StartCell) then
760 WriteInteger(Node, 'StartCell', StartCell.Id);
761
762 with Node do begin
763 NewNode := OwnerDocument.CreateElement('Map');
764 AppendChild(NewNode);
765 PlayerMap.SaveToNode(NewNode);
766 end;
767 with Node do begin
768 NewNode := OwnerDocument.CreateElement('UnitMoves');
769 AppendChild(NewNode);
770 Moves.SaveToNode(NewNode);
771 end;
772 with Node do begin
773 NewNode := OwnerDocument.CreateElement('TurnStats');
774 AppendChild(NewNode);
775 TurnStats.SaveToNode(NewNode);
776 end;
777end;
778
779constructor TPlayer.Create;
780begin
781 inherited;
782 Units := TUnits.Create(False);
783 Moves := TUnitMoves.Create;
784 Moves.Player := Self;
785 StartUnits := DefaultPlayerStartUnits;
786 StartCell := nil;
787 PlayerMap := TPlayerMap.Create;
788 PlayerMap.Player := Self;
789 TurnStats := TGameTurnStats.Create;
790end;
791
792destructor TPlayer.Destroy;
793var
794 I: Integer;
795begin
796 //Client := nil;
797 FreeAndNil(TurnStats);
798 FreeAndNil(PlayerMap);
799 FreeAndNil(Moves);
800 for I := Units.Count - 1 downto 0 do
801 Units[I].Player := nil;
802 FreeAndNil(Units);
803 inherited;
804end;
805
806procedure TPlayer.Assign(Source: TItem);
807begin
808 inherited;
809 //Game := TPlayer(Source).Game;
810 TotalCells := TPlayer(Source).TotalCells;
811 TotalUnits := TPlayer(Source).TotalUnits;
812 TotalCities := TPlayer(Source).TotalCities;
813 TotalDiscovered := TPlayer(Source).TotalDiscovered;
814 TotalWinObjectiveCells := TPlayer(Source).TotalWinObjectiveCells;
815 StartCell := TPlayer(Source).StartCell;
816 Color := TPlayer(Source).Color;
817 //Units.Assign(TPlayer(Source).Units);
818 Nation := TPlayer(Source).Nation;
819end;
820
821procedure TPlayer.LoadConfig(Config: TXmlConfig; Path: string);
822begin
823 with Config do begin
824 Self.Name := string(GetValue(DOMString(Path + '/Name'), ''));
825 Color := TColor(GetValue(DOMString(Path + '/Color'), 0));
826 StartUnits := GetValue(DOMString(Path + '/StartUnits'), 5);
827 Mode := TPlayerMode(GetValue(DOMString(Path + '/Mode'), 0));
828 Defensive := GetValue(DOMString(Path + '/Defensive'), False);
829 Agressivity := TComputerAgressivity(GetValue(DOMString(Path + '/Agressivity'), 0));
830 Nation := TNation(TGame(Game).GameSystem.Nations.FindById(GetValue(DOMString(Path + '/Nation'), 0)));
831 end;
832end;
833
834procedure TPlayer.SaveConfig(Config: TXmlConfig; Path: string);
835begin
836 with Config do begin
837 SetValue(DOMString(Path + '/Name'), DOMString(Self.Name));
838 SetValue(DOMString(Path + '/Color'), Integer(Color));
839 SetValue(DOMString(Path + '/StartUnits'), StartUnits);
840 SetValue(DOMString(Path + '/Mode'), Integer(Mode));
841 SetValue(DOMString(Path + '/Defensive'), Defensive);
842 SetValue(DOMString(Path + '/Agressivity'), Integer(Agressivity));
843 if Assigned(Nation) then
844 SetValue(DOMString(Path + '/Nation'), Nation.Id)
845 else SetValue(DOMString(Path + '/Nation'), 0)
846 end;
847end;
848
849procedure TPlayer.Attack(var AttackPower, DefendPower: Integer);
850var
851 AttackerDiceCount: Integer;
852 DefenderDiceCount: Integer;
853 S: string;
854 I: Integer;
855 AttackRolls: TList<Integer>;
856 DefendRolls: TList<Integer>;
857begin
858 AttackRolls := TList<Integer>.Create;
859 DefendRolls := TList<Integer>.Create;
860 if AttackPower < 1 then
861 raise Exception.Create(SAttackerPowerPositive);
862 if DefendPower < 0 then
863 raise Exception.Create(SDefenderPowerPositive);
864 while (AttackPower > 0) and (DefendPower > 0) do begin
865 // Risk game rules:
866 // Each side do their dice roll and compare result. Defender wins tie.
867 // Attacker can use three dices and defender two
868 AttackerDiceCount := Min(AttackPower, 3);
869 DefenderDiceCount := Min(DefendPower, 2);
870 // Roll and sort numbers
871 AttackRolls.Count := AttackerDiceCount;
872 for I := 0 to AttackerDiceCount - 1 do begin
873 AttackRolls[I] := Random(7);
874 end;
875 AttackRolls.Sort(TComparer<Integer>.Construct(ComparePointer));
876 S := 'Att:';
877 for I := 0 to AttackerDiceCount - 1 do
878 S := S + IntToStr(Integer(AttackRolls[I])) + ', ';
879 DefendRolls.Count := DefenderDiceCount;
880 for I := 0 to DefenderDiceCount - 1 do begin
881 DefendRolls[I] := Random(7);
882 end;
883 DefendRolls.Sort(TComparer<Integer>.Construct(ComparePointer));
884 S := S + ' Def:';
885 for I := 0 to DefenderDiceCount - 1 do
886 S := S + IntToStr(Integer(DefendRolls[I])) + ', ';
887 // Resolution
888 for I := 0 to Min(AttackerDiceCount, DefenderDiceCount) - 1 do
889 if AttackRolls[I] > DefendRolls[I] then Dec(DefendPower)
890 else Dec(AttackPower);
891 end;
892 FreeAndNil(AttackRolls);
893 FreeAndNil(DefendRolls);
894end;
895
896function CellCompare(constref Item1, Item2: TPlayerCell): Integer;
897var
898 Stack1, Stack2: Integer;
899begin
900 if Assigned(Item1.MapCell.OneUnit) then Stack1 := Item1.MapCell.OneUnit.Power
901 else Stack1 := 0;
902 if Assigned(Item2.MapCell.OneUnit) then Stack2 := Item2.MapCell.OneUnit.Power
903 else Stack2 := 0;
904 if Stack1 > Stack1 then Result := 1
905 else if Stack1 < Stack2 then Result := -1
906 else Result := 0;
907end;
908
909function CellCompareDescending(constref Item1, Item2: TPlayerCell): Integer;
910var
911 Stack1, Stack2: Integer;
912begin
913 if Assigned(Item1.MapCell.OneUnit) then Stack1 := Item1.MapCell.OneUnit.Power
914 else Stack1 := 0;
915 if Assigned(Item2.MapCell.OneUnit) then Stack2 := Item2.MapCell.OneUnit.Power
916 else Stack2 := 0;
917 if Stack1 > Stack2 then Result := -1
918 else if Stack1 < Stack2 then Result := 1
919 else Result := 0;
920end;
921
922procedure TPlayer.MoveUnit(UnitMove: TUnitMove);
923var
924 AttackerPower: Integer;
925 DefenderPower: Integer;
926 UnitCount: Integer;
927begin
928 with UnitMove do begin
929 UnitCount := CountOnce;
930 if CountOnce > CellFrom.MapCell.OneUnit.Power then
931 UnitCount := CellFrom.MapCell.OneUnit.Power;
932 CountOnce := 0;
933 if not Assigned(CellTo.MapCell.OneUnit) then begin
934 CellTo.MapCell.OneUnit := TGame(Game).Units.AddNew(CellFrom.MapCell.OneUnit.Kind, 0);
935 CellTo.MapCell.OneUnit.Player := Self;
936 CellTo.MapCell.Player := Self;
937 end;
938
939 if CellTo.MapCell.OneUnit.Player = Self then begin
940 // Inner move
941 CellTo.MapCell.OneUnit.Power := CellTo.MapCell.OneUnit.Power + UnitCount;
942 Dec(CellTo.MapCell.OneUnit.Moves);
943 end else begin
944 AttackerPower := UnitCount;
945 if Assigned(CellTo.MapCell.OneUnit) then
946 DefenderPower := CellTo.MapCell.OneUnit.Power
947 else DefenderPower := 0;
948 Attack(AttackerPower, DefenderPower);
949 if DefenderPower = 0 then begin
950 // Attacker wins with possible loses
951 ClearMovesFromCell(CellTo);
952 CellTo.MapCell.Player := Self;
953 CellTo.MapCell.OneUnit.Player := Self;
954 CellTo.MapCell.OneUnit.Power := AttackerPower;
955 end else
956 if AttackerPower = 0 then begin
957 // Defender wins with possible loses
958 CellTo.MapCell.OneUnit.Power := DefenderPower;
959 CellTo.MapCell.OneUnit.Moves := CellFrom.MapCell.OneUnit.Moves - 1;
960 end else
961 raise Exception.Create(SUnfinishedBattle);
962 end;
963 CellFrom.MapCell.OneUnit.Power := CellFrom.MapCell.OneUnit.Power - UnitCount;
964 CellFrom.MapCell.CheckOwnership;
965 end;
966
967 // Clean from cell from empty units
968 if UnitMove.CellFrom.MapCell.OneUnit.Power = 0 then begin
969 UnitMove.CellFrom.MapCell.OneUnit.Player := nil;
970 UnitMove.CellFrom.MapCell.OneUnit := nil;
971 end;
972
973 // Remove empty move
974 if (UnitMove.CellFrom.MapCell.Player = Self) and
975 (UnitMove.CountOnce = 0) and (UnitMove.CountRepeat = 0) then
976 Moves.Remove(UnitMove);
977end;
978
979procedure TPlayer.MoveAll;
980var
981 I: Integer;
982begin
983 for I := Moves.Count - 1 downto 0 do
984 with Moves[I] do
985 if CountOnce > 0 then begin
986 if CellFrom.MapCell.Player = Self then begin
987 MoveUnit(Moves[I]);
988 end;
989 end;
990end;
991
992procedure TPlayer.ReduceMovesPower;
993var
994 UnitMove: TUnitMove;
995 Power: Integer;
996begin
997 // Power of cell can be reduced by unsucessful enemy attack
998 for UnitMove in Moves do begin
999 Power := UnitMove.CellFrom.GetAvialPower;
1000 if Power < 0 then begin
1001 if Abs(Power) < UnitMove.CountOnce then
1002 UnitMove.CountOnce := UnitMove.CountOnce - Abs(Power)
1003 else UnitMove.CountOnce := 0;
1004 end;
1005 end;
1006end;
1007
1008procedure TPlayer.RemoveInvalidMoves;
1009var
1010 I: Integer;
1011begin
1012 for I := Moves.Count - 1 downto 0 do
1013 if Moves[I].CellFrom.MapCell.Player <> Self then
1014 Moves.Delete(I);
1015end;
1016
1017procedure TPlayer.RemoveEmptyUnits;
1018var
1019 I: Integer;
1020 OneUnit: TUnit;
1021begin
1022 for I := 0 to PlayerMap.Cells.Count - 1 do
1023 with PlayerMap.Cells[I] do begin
1024 if Assigned(MapCell.OneUnit) and (MapCell.OneUnit.Power = 0) then begin
1025 OneUnit := MapCell.OneUnit;
1026 MapCell.OneUnit := nil;
1027 OneUnit.Player := nil;
1028 end;
1029 end;
1030end;
1031
1032procedure TPlayer.InitUnitMoves;
1033var
1034 I: Integer;
1035begin
1036 for I := 0 to Units.Count - 1 do
1037 Units[I].Moves := Units[I].Kind.Moves;
1038end;
1039
1040procedure TPlayer.ClearMovesFromCell(Cell: TPlayerCell);
1041var
1042 I: Integer;
1043 OtherPlayerCell: TPlayerCell;
1044begin
1045 if Assigned(Cell.MapCell.Player) then
1046 with TPlayer(Cell.MapCell.Player) do begin
1047 OtherPlayerCell := PlayerMap.Cells.SearchCell(Cell.MapCell);
1048 if Assigned(OtherPlayerCell) then begin
1049 for I := OtherPlayerCell.MovesFrom.Count - 1 downto 0 do
1050 Moves.Remove(OtherPlayerCell.MovesFrom[I]);
1051 end;
1052 end;
1053end;
1054
1055function TPlayer.SetMove(CellFrom, CellTo: TPlayerCell; Power: Integer; Confirmation: Boolean = True): TUnitMove;
1056var
1057 NewMove: TUnitMove;
1058 CountOnce: Integer;
1059 CountRepeat: Integer;
1060 Confirm: Boolean;
1061begin
1062 if CellFrom.MapCell.Player <> Self then
1063 raise Exception.Create('Can''t set move of other player.');
1064 Confirm := True;
1065 Result := Moves.SearchByFromTo(CellFrom, CellTo);
1066
1067 if Assigned(Result) then begin
1068 // Update existing move
1069 CountOnce := Result.CountOnce;
1070 CountRepeat := Result.CountRepeat;
1071 if (Mode = pmHuman) and Confirmation and
1072 Assigned(FOnMove) then
1073 FOnMove(CellFrom, CellTo, CountOnce, CountRepeat, True, Confirm);
1074 end else begin
1075 // Create new move
1076 CountOnce := Power;
1077 CountRepeat := 0;
1078 if (Mode = pmHuman) and Confirmation and
1079 Assigned(FOnMove) then
1080 FOnMove(CellFrom, CellTo, CountOnce, CountRepeat, False, Confirm);
1081 end;
1082 if Confirm then begin
1083 if Assigned(Result) then begin
1084 // Already have such move
1085 if (CountOnce = 0) and (CountRepeat = 0) then begin
1086 Result.List.Remove(Result);
1087 end else begin
1088 Result.CountOnce := CountOnce;
1089 Result.CountRepeat := CountRepeat;
1090 CheckCounterMove(Result);
1091 end;
1092 end else begin
1093 // Add new move
1094 if (CountOnce > 0) or (CountRepeat > 0) then begin
1095 NewMove := TUnitMove.Create;
1096 NewMove.List := Moves;
1097 NewMove.CellFrom := CellFrom;
1098 NewMove.CellTo := CellTo;
1099 NewMove.CountOnce := CountOnce;
1100 NewMove.CountRepeat := CountRepeat;
1101 Moves.Add(NewMove);
1102 Result := NewMove;
1103 CheckCounterMove(NewMove);
1104 if TGame(Game).GameSystem.UnitsMoveImmediately then begin
1105 MoveUnit(NewMove);
1106 end;
1107 end;
1108 end;
1109 if Assigned(TGame(Game).OnMoveUpdated) then
1110 TGame(Game).OnMoveUpdated(Result);
1111 end;
1112end;
1113
1114procedure TPlayer.UpdateRepeatMoves;
1115var
1116 Move: TUnitMove;
1117 AvailPower: Integer;
1118begin
1119 for Move in Moves do
1120 with Move do begin
1121 if CellFrom.MapCell.Player = Self then begin
1122 AvailPower := CellFrom.GetAvialPower + Move.CountOnce;
1123 if CountRepeat <= AvailPower then
1124 CountOnce := CountRepeat
1125 else CountOnce := AvailPower;
1126 end;
1127 end;
1128end;
1129
1130procedure TPlayer.UpdateEmptyCellsNeutral;
1131var
1132 I: Integer;
1133begin
1134 if TGame(Game).GameSystem.EmptyCellsNeutral then
1135 for I := 0 to PlayerMap.Cells.Count - 1 do
1136 with TPlayerCell(PlayerMap.Cells[I]) do begin
1137 MapCell.CheckOwnership;
1138 end;
1139end;
1140
1141procedure TPlayer.Reset;
1142begin
1143 Moves.Clear;
1144 PlayerMap.Cells.Clear;
1145 TotalUnits := 0;
1146 TotalCells := 0;
1147 TotalCities := 0;
1148 TotalDiscovered := 0;
1149 TotalWinObjectiveCells := 0;
1150 TurnStats.Clear;
1151 StartCell := nil;
1152end;
1153
1154procedure TPlayer.Surrender;
1155var
1156 I: Integer;
1157begin
1158 Moves.Clear;
1159 for I := 0 to PlayerMap.Cells.Count - 1 do
1160 if PlayerMap.Cells[I].MapCell.Player = Self then
1161 PlayerMap.Cells[I].MapCell.Player := nil;
1162end;
1163
1164function TPlayer.IsAlive: Boolean;
1165begin
1166 Result := (TotalCells > 0) and Assigned(StartCell);
1167end;
1168
1169procedure TPlayer.CheckCounterMove(Move: TUnitMove);
1170var
1171 CounterMove: TUnitMove;
1172begin
1173 CounterMove := Moves.SearchByFromTo(Move.CellTo, Move.CellFrom);
1174 if Assigned(CounterMove) then begin
1175 // For now, just remove counter move
1176 Moves.Remove(CounterMove);
1177 end;
1178end;
1179
1180procedure TPlayer.SetMode(AValue: TPlayerMode);
1181begin
1182 if FMode = AValue then Exit;
1183 FMode := AValue;
1184end;
1185
1186class function TPlayer.GetFields: TItemFields;
1187var
1188 Field: TItemField;
1189begin
1190 Result := inherited;
1191 Field := Result.AddField(2, 'Nation', SNation, dtReference);
1192 Field := Result.AddField(3, 'Mode', SMode, dtEnumeration);
1193 Field.EnumStates.Add(SHuman);
1194 Field.EnumStates.Add(SComputer);
1195 Result.AddField(4, 'Color', SColor, dtColor);
1196 Field := Result.AddField(5, 'Agressivity', SAgressivity, dtEnumeration);
1197 Field.EnumStates.Add(SLow);
1198 Field.EnumStates.Add(SMedium);
1199 Field.EnumStates.Add(SHigh);
1200 Field.VisibleIfIndex := 2;
1201 Field := Result.AddField(6, 'Defensive', SDefensive, dtBoolean);
1202 Field.VisibleIfIndex := 2;
1203 Result.AddField(7, 'StartUnits', SStartUnits, dtInteger);
1204end;
1205
1206procedure TPlayer.GetValue(Index: Integer; out Value);
1207begin
1208 if Index = 1 then string(Value) := Name
1209 else if Index = 2 then TNation(Value) := Nation
1210 else if Index = 3 then TPlayerMode(Value) := Mode
1211 else if Index = 4 then TColor(Value) := Color
1212 else if Index = 5 then TComputerAgressivity(Value) := Agressivity
1213 else if Index = 6 then Boolean(Value) := Defensive
1214 else if Index = 7 then Integer(Value) := StartUnits
1215 else inherited;
1216end;
1217
1218procedure TPlayer.SetValue(Index: Integer; var Value);
1219begin
1220 if Index = 1 then Name := string(Value)
1221 else if Index = 2 then Nation := TNation(Value)
1222 else if Index = 3 then Mode := TPlayerMode(Value)
1223 else if Index = 4 then Color := TColor(Value)
1224 else if Index = 5 then Agressivity := TComputerAgressivity(Value)
1225 else if Index = 6 then Defensive := Boolean(Value)
1226 else if Index = 7 then StartUnits := Integer(Value)
1227 else inherited;
1228end;
1229
1230function TPlayer.GetReferenceList(Index: Integer): TItemList;
1231begin
1232 if Index = 2 then Result := TGame(Game).GameSystem.Nations
1233 else Result := nil;
1234end;
1235
1236class function TPlayer.GetClassSysName: string;
1237begin
1238 Result := 'Player';
1239end;
1240
1241class function TPlayer.GetClassName: string;
1242begin
1243 Result := SPlayer;
1244end;
1245
1246function TPlayer.IsAllowedMoveTarget(CellFrom, CellTo: TPlayerCell): Boolean;
1247var
1248 CellToHasOwnUnit: Boolean;
1249 UnitHasMoveEnergy: Boolean;
1250begin
1251 CellToHasOwnUnit := Assigned(CellTo.MapCell.OneUnit) and
1252 (CellTo.MapCell.OneUnit.Player = Self) and (CellTo.MapCell.OneUnit.Power > 0);
1253 UnitHasMoveEnergy := Assigned(CellFrom.MapCell.OneUnit) and
1254 (CellFrom.MapCell.OneUnit.Moves > 0);
1255 Result := UnitHasMoveEnergy and
1256 TGame(Game).Map.IsCellsNeighbor(CellFrom.MapCell, CellTo.MapCell)
1257 and (TGame(Game).GameSystem.UnitsSplitMerge or
1258 (not TGame(Game).GameSystem.UnitsSplitMerge and not CellToHasOwnUnit));
1259end;
1260
1261procedure TPlayer.Grow;
1262var
1263 I: Integer;
1264 Addition: Integer;
1265 Dies: Integer;
1266 NewUnit: TUnit;
1267begin
1268 with TGame(Game).Map do
1269 for I := 0 to Cells.Count - 1 do
1270 with Cells[I] do begin
1271 if (Player = Self) and ((TGame(Game).GrowCells = gcPlayerAll) or
1272 ((TGame(Game).GrowCells = gcPlayerCities) and (Assigned(Building) and (Building.Kind.SpecialType = stCity)))) then begin
1273 if not Assigned(OneUnit) then begin
1274 NewUnit := TGame(Game).Units.AddNew(TUnitKind(TGame(Game).GameSystem.UnitKinds.First), 0);
1275 NewUnit.Player := Self;
1276 NewUnit.MapCell := Cells[I];
1277 end;
1278 if OneUnit.Power < MaxPower then begin
1279 // Increase units count
1280 Addition := 0;
1281 if TGame(Game).GrowAmount = gaByOne then begin
1282 Addition := 1;
1283 end else
1284 if TGame(Game).GrowAmount = gaBySquareRoot then begin
1285 Addition := Trunc(Sqrt(OneUnit.Power));
1286 if Addition = 0 then Addition := 1;
1287 end;
1288 if Assigned(Building) and (Building.Kind.BonusGrow > 0) then
1289 OneUnit.Power := OneUnit.Power + Trunc(OneUnit.Power * Building.Kind.BonusGrow / 100);
1290 OneUnit.Power := Min(OneUnit.Power + Addition, MaxPower);
1291 end else
1292 if OneUnit.Power > MaxPower then begin
1293 // Reduce units count
1294 // If cell has more then MaxPower units then additional units dies
1295 // in twice of squeare root of unites over MaxPower
1296 Dies := 2 * Trunc(Sqrt(OneUnit.Power - MaxPower));
1297 OneUnit.Power := Max(OneUnit.Power - Dies, 0);
1298 end;
1299 end;
1300 end;
1301end;
1302
1303end.
1304
Note: See TracBrowser for help on using the repository browser.