source: tags/1.4.0/Map.pas

Last change on this file was 409, checked in by chronos, 2 weeks ago

Merged revision(s) 403-408 from trunk:

  • Added: Cover image.
  • Fixed: Crash on cyclic map new move creation.
  • Modified: TView class moved into separate unit.
  • Added: French translation.
  • Fixed: Do not stop running game on Quit action.
  • Fixed: Cell cities were not correctly stored the saved game.
  • Fixed: ItemList references were loaded by item index instead of item id.
  • Fixed: Wrong default map image path initialization.
File size: 32.1 KB
Line 
1unit Map;
2
3interface
4
5uses
6 Classes, SysUtils, Graphics, ExtCtrls, Geometry, DOM, Generics.Collections,
7 Generics.Defaults, Building, XML, Units;
8
9const
10 DefaultMaxPower = 99;
11
12type
13 TMapArea = class;
14 TMap = class;
15 TCells = class;
16 TCellLinks = class;
17
18 TTerrainType = (ttVoid, ttNormal, ttCityUnused);
19 TExtraType = (etNone, etObjectiveTarget, etAttack, etDefense, etLookout,
20 etGrowLow, etGrowMedium, etGrowHigh);
21
22 { TCell }
23
24 TCell = class
25 private
26 FArea: TMapArea;
27 FBuilding: TBuilding;
28 FId: Integer;
29 FMap: TMap;
30 FUnit: TUnit;
31 procedure SetArea(AValue: TMapArea);
32 procedure SetBuilding(AValue: TBuilding);
33 procedure SetId(AValue: Integer);
34 procedure SetUnit(AValue: TUnit);
35 public
36 PosPx: TPoint;
37 Polygon: TPolygon;
38 Terrain: TTerrainType;
39 PlayerId: Integer; // Temporary value
40 Player: TObject; // TPlayer;
41 NeighborsId: array of Integer;
42 Neighbors: TCells;
43 Mark: Boolean; // Temporary value
44 Weight: Integer; // Temporary value
45 Angle: Double; // Temporary value
46 PlayerCell: TObject; // Temporary value
47 Links: TCellLinks;
48 Extra: TExtraType;
49 OneUnitId: Integer; // Temporary value
50 BuildingId: Integer; // Temporary value
51 function Compare(Cell: TCell): Boolean;
52 procedure ConnectTo(Cell: TCell);
53 procedure DisconnectFrom(Cell: TCell);
54 function NeighboringToVoid: Boolean;
55 procedure AreaExtend;
56 procedure FixRefId;
57 procedure LoadFromNode(Node: TDOMNode);
58 procedure SaveToNode(Node: TDOMNode);
59 procedure Assign(Source: TCell);
60 function GetColor: TColor;
61 function ToString: ansistring; override;
62 procedure CheckOwnership;
63 constructor Create;
64 destructor Destroy; override;
65 property Id: Integer read FId write SetId;
66 property Map: TMap read FMap write FMap;
67 property Area: TMapArea read FArea write SetArea;
68 property OneUnit: TUnit read FUnit write SetUnit;
69 property Building: TBuilding read FBuilding write SetBuilding;
70 end;
71
72 TCellArray = array of TCell;
73
74 { TCells }
75
76 TCells = class(TObjectList<TCell>)
77 Map: TMap;
78 procedure FixRefId;
79 function FindById(Id: Integer): TCell;
80 procedure GetCellsWithWeight(List: TCells; Low, High: Integer);
81 procedure GetCellsWithExtra(List: TCells; Extra: TExtraType);
82 procedure LoadFromNode(Node: TDOMNode);
83 procedure SaveToNode(Node: TDOMNode);
84 function Compare(Cells: TCells): Boolean;
85 procedure ClearMark;
86 procedure ClearWeight;
87 function ToString: ansistring; override;
88 end;
89
90 { TCellLink }
91
92 TCellLink = class
93 Points: array of TPoint;
94 Cells: TCells;
95 Map: TMap;
96 procedure LoadFromNode(Node: TDOMNode);
97 procedure SaveToNode(Node: TDOMNode);
98 constructor Create;
99 destructor Destroy; override;
100 end;
101
102 { TCellLinks }
103
104 TCellLinks = class(TObjectList<TCellLink>)
105 Map: TMap;
106 function FindByCells(Cell1, Cell2: TCell): TCellLink;
107 function AddLink(Cell1, Cell2: TCell): TCellLink;
108 procedure LoadFromNode(Node: TDOMNode);
109 procedure SaveToNode(Node: TDOMNode);
110 end;
111
112 { TCellLinkParams }
113
114 TCellLinkParams = class
115 Cell1: TCell;
116 Cell2: TCell;
117 Distance: Double;
118 Angle: Double;
119 end;
120
121 TMapShape = (msRectangle, msImage, msRounded);
122
123 { TMapArea }
124
125 TMapArea = class
126 Id: Integer;
127 Map: TMap;
128 BridgeCount: Integer;
129 Cells: TCells;
130 procedure GetBorderCells(List: TCells);
131 constructor Create;
132 destructor Destroy; override;
133 end;
134
135 TMapAreas = class(TObjectList<TMapArea>)
136 end;
137
138 { TMap }
139
140 TMap = class
141 private
142 function GetPixelRect: TRect;
143 function GetSize: TPoint; virtual;
144 function SearchDifferentCellArea(List: TCells; SourceArea,
145 DestArea: TMapArea): TCell;
146 protected
147 FSize: TPoint;
148 FPixelRect: TRect;
149 FNewCellId: Integer;
150 function GetNewCellId: Integer; virtual;
151 procedure SortNeighborsByAngle;
152 procedure SetSize(AValue: TPoint); virtual;
153 public
154 Game: TObject; //TGame;
155 MaxPower: Integer;
156 DefaultCellSize: TPoint;
157 Cells: TCells;
158 Shape: TMapShape;
159 Image: TImage;
160 CellLinks: TCellLinks;
161 Areas: TMapAreas;
162 Cyclic: Boolean;
163 function IsOutsideShape(Coord: TPoint): Boolean; virtual;
164 function IsCellsNeighbor(Cell1, Cell2: TCell): Boolean; virtual;
165 function IsValidIndex(Index: TPoint): Boolean; virtual;
166 procedure Assign(Source: TMap); virtual;
167 function Compare(Map: TMap): Boolean;
168 procedure LoadFromFile(FileName: string); virtual;
169 procedure SaveToFile(FileName: string); virtual;
170 procedure LoadFromNode(Node: TDOMNode);
171 procedure SaveToNode(Node: TDOMNode);
172 function PosToCell(Pos: TPoint): TCell; virtual;
173 function CellToPos(Cell: TCell): TPoint; virtual;
174 procedure ComputePlayerStats; virtual;
175 procedure Generate; virtual;
176 procedure BuildMapAreas;
177 procedure BuildBridges;
178 procedure MakeSymetric;
179 procedure CreateLinks;
180 procedure Clear;
181 procedure CheckCells;
182 function ToString: string; override;
183 constructor Create; virtual;
184 destructor Destroy; override;
185 function CalculatePixelRect: TRect; virtual;
186 procedure ForEachCells(Method: TMethod); virtual;
187 property Size: TPoint read GetSize write SetSize;
188 property PixelRect: TRect read GetPixelRect;
189 end;
190
191
192resourcestring
193 SCellRemoveNeighborError = 'Can''t remove cell from neighbour cell';
194 SNegativeCellPowerNotAllowed = 'Not allowed to substract power under zero to negative value';
195
196implementation
197
198uses
199 Player, Game;
200
201{ TCellLink }
202
203procedure TCellLink.LoadFromNode(Node: TDOMNode);
204var
205 Node2: TDOMNode;
206 Node3: TDOMNode;
207begin
208 Node3 := Node.FindNode('Points');
209 if Assigned(Node3) then begin
210 SetLength(Points, 0);
211 Node2 := Node3.FirstChild;
212 while Assigned(Node2) and (Node2.NodeName = 'Point') do begin
213 SetLength(Points, Length(Points) + 1);
214 Points[High(Points)].X := ReadInteger(Node2, 'X', 0);
215 Points[High(Points)].Y := ReadInteger(Node2, 'Y', 0);
216 Node2 := Node2.NextSibling;
217 end;
218 end;
219end;
220
221procedure TCellLink.SaveToNode(Node: TDOMNode);
222var
223 NewNode: TDOMNode;
224 NewNode2: TDOMNode;
225 I: Integer;
226begin
227 NewNode := Node.OwnerDocument.CreateElement('Points');
228 Node.AppendChild(NewNode);
229 for I := 0 to Length(Points) - 1 do begin
230 NewNode2 := NewNode.OwnerDocument.CreateElement('Point');
231 NewNode.AppendChild(NewNode2);
232 WriteInteger(NewNode2, 'X', Points[I].X);
233 WriteInteger(NewNode2, 'Y', Points[I].Y);
234 end;
235end;
236
237constructor TCellLink.Create;
238begin
239 Cells := TCells.Create;
240 Cells.OwnsObjects := False;
241end;
242
243destructor TCellLink.Destroy;
244var
245 I: Integer;
246begin
247 for I := 0 to Cells.Count - 1 do begin
248 if Cells[I].Neighbors.Remove(Cells[1 - I]) = -1 then
249 raise Exception.Create(SCellRemoveNeighborError);
250 if Cells[I].Links.Remove(Self) = -1 then
251 raise Exception.Create(SCellRemoveNeighborError);
252 end;
253 FreeAndNil(Cells);
254 inherited;
255end;
256
257{ TCellLinks }
258
259function TCellLinks.FindByCells(Cell1, Cell2: TCell): TCellLink;
260var
261 I: Integer;
262begin
263 I := 0;
264 while (I < Count) do begin
265 if ((Items[I].Cells[0] = Cell1) and (Items[I].Cells[1] = Cell2)) or
266 ((Items[I].Cells[0] = Cell2) and (Items[I].Cells[1] = Cell1)) then
267 Break;
268 Inc(I);
269 end;
270 if I < Count then Result := Items[I]
271 else Result := nil;
272end;
273
274function TCellLinks.AddLink(Cell1, Cell2: TCell): TCellLink;
275begin
276 Result := TCellLink.Create;
277 Cell1.ConnectTo(Cell2);
278 Cell1.Links.Add(Result);
279 Cell2.Links.Add(Result);
280 SetLength(Result.Points, 2);
281 Result.Cells.Add(Cell1);
282 Result.Points[0] := Cell1.PosPx;
283 Result.Cells.Add(Cell2);
284 Result.Points[1] := Cell2.PosPx;
285 Result.Map := Map;
286 Map.CellLinks.Add(Result);
287end;
288
289procedure TCellLinks.LoadFromNode(Node: TDOMNode);
290var
291 Node2: TDOMNode;
292 NewCell: TCellLink;
293begin
294 Count := 0;
295 Node2 := Node.FirstChild;
296 while Assigned(Node2) and (Node2.NodeName = 'CellLink') do begin
297 NewCell := TCellLink.Create;
298 //NewCell.Map := Map;
299 NewCell.LoadFromNode(Node2);
300 Add(NewCell);
301 Node2 := Node2.NextSibling;
302 end;
303end;
304
305procedure TCellLinks.SaveToNode(Node: TDOMNode);
306var
307 I: Integer;
308 NewNode2: TDOMNode;
309begin
310 for I := 0 to Count - 1 do
311 with Items[I] do begin
312 NewNode2 := Node.OwnerDocument.CreateElement('CellLink');
313 Node.AppendChild(NewNode2);
314 SaveToNode(NewNode2);
315 end;
316end;
317
318{ TMapArea }
319
320procedure TMapArea.GetBorderCells(List: TCells);
321var
322 Cell: TCell;
323begin
324 List.Clear;
325 Map.Cells.ClearMark;
326 for Cell in Cells do begin
327 if Cell.NeighboringToVoid and (Cell.Area = Self) and (not Cell.Mark) then begin
328 List.Add(Cell);
329 Cell.Mark := True;
330 end;
331 end;
332end;
333
334constructor TMapArea.Create;
335begin
336 Cells := TCells.Create;
337 Cells.OwnsObjects := False;
338end;
339
340destructor TMapArea.Destroy;
341begin
342 FreeAndNil(Cells);
343 inherited;
344end;
345
346{ TCells }
347
348procedure TCells.FixRefId;
349var
350 I: Integer;
351begin
352 for I := 0 to Count - 1 do
353 Items[I].FixRefId;
354end;
355
356function TCells.FindById(Id: Integer): TCell;
357var
358 I: Integer;
359begin
360 I := 0;
361 while (I < Count) and (Items[I].Id <> Id) do Inc(I);
362 if I < Count then Result := Items[I]
363 else Result := nil;
364end;
365
366procedure TCells.GetCellsWithWeight(List: TCells; Low, High: Integer);
367var
368 Cell: TCell;
369begin
370 List.Clear;
371 for Cell in Self do
372 if (Cell.Terrain <> ttVoid) and (Cell.Weight >= Low) and
373 (Cell.Weight <= High) then List.Add(Cell);
374end;
375
376procedure TCells.GetCellsWithExtra(List: TCells; Extra: TExtraType);
377var
378 Cell: TCell;
379begin
380 List.Clear;
381 for Cell in Self do
382 if Cell.Extra = Extra then List.Add(Cell);
383end;
384
385procedure TCells.LoadFromNode(Node: TDOMNode);
386var
387 Node2: TDOMNode;
388 NewCell: TCell;
389begin
390 Count := 0;
391 Node2 := Node.FirstChild;
392 while Assigned(Node2) and (Node2.NodeName = 'Cell') do begin
393 NewCell := TCell.Create;
394 NewCell.Map := Map;
395 NewCell.LoadFromNode(Node2);
396 Add(NewCell);
397 Node2 := Node2.NextSibling;
398 end;
399end;
400
401procedure TCells.SaveToNode(Node: TDOMNode);
402var
403 I: Integer;
404 NewNode2: TDOMNode;
405begin
406 for I := 0 to Count - 1 do
407 with Items[I] do begin
408 NewNode2 := Node.OwnerDocument.CreateElement('Cell');
409 Node.AppendChild(NewNode2);
410 SaveToNode(NewNode2);
411 end;
412end;
413
414function TCells.Compare(Cells: TCells): Boolean;
415var
416 I: Integer;
417begin
418 Result := True;
419 for I := 0 to Count - 1 do
420 with Items[I] do begin
421 if not Items[I].Compare(Cells[I]) then begin
422 Result := False;
423 Break;
424 end;
425 end;
426end;
427
428procedure TCells.ClearMark;
429var
430 Cell: TCell;
431begin
432 for Cell in Self do Cell.Mark := False;
433end;
434
435procedure TCells.ClearWeight;
436var
437 Cell: TCell;
438begin
439 for Cell in Self do Cell.Weight := 0;
440end;
441
442function TCells.ToString: ansistring;
443var
444 C: TCell;
445begin
446 Result := '';
447 for C in Self do
448 Result := Result + C.ToString + LineEnding;
449end;
450
451{ TMap }
452
453function TMap.GetSize: TPoint;
454begin
455 Result:= FSize;
456end;
457
458function TMap.GetPixelRect: TRect;
459begin
460 if FPixelRect.Empty then FPixelRect := CalculatePixelRect;
461 Result := FPixelRect;
462end;
463
464procedure TMap.SetSize(AValue: TPoint);
465begin
466 if (FSize.X <> AValue.X) or (FSize.Y <> AValue.Y) then begin
467 FSize := AValue;
468 end;
469end;
470
471function CompareCellAngle(constref C1, C2: TCell): Integer;
472begin
473 if C1.Angle < C2.Angle then Result := -1
474 else if C1.Angle > C2.Angle then Result := 1
475 else Result := 0;
476end;
477
478procedure TMap.SortNeighborsByAngle;
479var
480 Cell: TCell;
481 NeighborCell: TCell;
482begin
483 for Cell in Cells do begin
484 for NeighborCell in Cell.Neighbors do
485 NeighborCell.Angle := TLine.Create(Cell.PosPx, NeighborCell.PosPx).GetAngle;
486
487 Cell.Neighbors.Sort(TComparer<TCell>.Construct(CompareCellAngle));
488 end;
489end;
490
491function TMap.GetNewCellId: Integer;
492begin
493 Result := FNewCellId;
494 Inc(FNewCellId);
495end;
496
497function TMap.IsOutsideShape(Coord: TPoint): Boolean;
498var
499 Rect: TRect;
500 Color: TColor;
501 Pos: TPoint;
502 Center: TPoint;
503begin
504 case Shape of
505 msRectangle: Result := False;
506 msImage: begin
507 Rect := PixelRect;
508 with Image.Picture.Bitmap do begin
509 Pos := TPoint.Create(Trunc(Coord.X / Rect.Size.X * Width),
510 Trunc(Coord.Y / Rect.Size.Y * Height));
511 Color := Canvas.Pixels[Pos.X, Pos.Y];
512 end;
513 Result := Color <> clWhite;
514 end;
515 msRounded: begin
516 Rect := PixelRect;
517 Center := Rect.Center;
518 Result := Sqr(Coord.X - Center.X) / Sqr(Rect.Size.X div 2) +
519 Sqr(Coord.Y - Center.Y) / Sqr(Rect.Size.Y div 2) > 1;
520 end
521 else Result := False;
522 end;
523end;
524
525function TMap.IsCellsNeighbor(Cell1, Cell2: TCell): Boolean;
526begin
527 Result := Cell1.Neighbors.IndexOf(Cell2) <> -1;
528end;
529
530function TMap.IsValidIndex(Index: TPoint): Boolean;
531begin
532 Result := (Index.X >= 0) and (Index.X < Size.X) and
533 (Index.Y >= 0) and (Index.Y < Size.Y);
534end;
535
536procedure TMap.Assign(Source: TMap);
537//var
538// I: Integer;
539begin
540 // Do not assign Game field
541 MaxPower := Source.MaxPower;
542 Cyclic := Source.Cyclic;
543 Size := Source.Size;
544 DefaultCellSize := Source.DefaultCellSize;
545 Shape := Source.Shape;
546 Image.Picture.Bitmap.Assign(Source.Image.Picture.Bitmap);
547
548 // TODO: How to copy cells
549 {// Copy all cells
550 Cells.Count := 0;
551 Cells.Count := Source.Cells.Count;
552 for I := 0 to Cells.Count - 1 do begin
553 Cells[I] := TCell.Create;
554 Cells[I].Map := Self;
555 Cells[I].Assign(Source.Cells[I]);
556 end;
557 }
558end;
559
560function TMap.Compare(Map: TMap): Boolean;
561begin
562 Result := (MaxPower = Map.MaxPower) and
563 (Cyclic = Map.Cyclic) and
564 (Size = Map.Size) and
565 (DefaultCellSize = Map.DefaultCellSize) and
566 (Shape = Map.Shape) and
567 Cells.Compare(Map.Cells);
568end;
569
570procedure TMap.LoadFromFile(FileName: string);
571begin
572end;
573
574procedure TMap.SaveToFile(FileName: string);
575begin
576end;
577
578procedure TMap.LoadFromNode(Node: TDOMNode);
579var
580 Node2: TDOMNode;
581begin
582 Size := TPoint.Create(ReadInteger(Node, 'SizeX', 0), ReadInteger(Node, 'SizeY', 0));
583 DefaultCellSize.X := ReadInteger(Node, 'DefaultCellSizeX', 1);
584 DefaultCellSize.Y := ReadInteger(Node, 'DefaultCellSizeY', 1);
585 MaxPower := ReadInteger(Node, 'MaxPower', DefaultMaxPower);
586 Cyclic := ReadBoolean(Node, 'Cyclic', False);
587 Shape := TMapShape(ReadInteger(Node, 'Shape', Integer(msRectangle)));
588 Node2 := Node.FindNode('Cells');
589 if Assigned(Node2) then
590 Cells.LoadFromNode(Node2);
591 Node2 := Node.FindNode('CellLinks');
592 if Assigned(Node2) then
593 CellLinks.LoadFromNode(Node2);
594 FPixelRect := CalculatePixelRect;
595end;
596
597procedure TMap.SaveToNode(Node: TDOMNode);
598var
599 NewNode: TDOMNode;
600begin
601 WriteInteger(Node, 'DefaultCellSizeX', DefaultCellSize.X);
602 WriteInteger(Node, 'DefaultCellSizeY', DefaultCellSize.Y);
603 WriteInteger(Node, 'MaxPower', MaxPower);
604 WriteBoolean(Node, 'Cyclic', Cyclic);
605 WriteInteger(Node, 'Shape', Integer(Shape));
606 WriteInteger(Node, 'SizeX', Size.X);
607 WriteInteger(Node, 'SizeY', Size.Y);
608 NewNode := Node.OwnerDocument.CreateElement('Cells');
609 Node.AppendChild(NewNode);
610 Cells.SaveToNode(NewNode);
611 NewNode := Node.OwnerDocument.CreateElement('CellLinks');
612 Node.AppendChild(NewNode);
613 CellLinks.SaveToNode(NewNode);
614end;
615
616function TMap.PosToCell(Pos: TPoint): TCell;
617var
618 I: Integer;
619begin
620 Result := nil;
621 for I := 0 to Cells.Count - 1 do
622 if Cells[I].Terrain <> ttVoid then begin
623 if Cells[I].Polygon.IsPointInside(Pos) then begin
624 Result := Cells[I];
625 Exit;
626 end;
627 end;
628end;
629
630function TMap.CellToPos(Cell: TCell): TPoint;
631begin
632 Result := Cell.PosPx;
633end;
634
635procedure TMap.ComputePlayerStats;
636var
637 Cell: TCell;
638begin
639 for Cell in Cells do
640 with Cell do begin
641 if Assigned(Player) then
642 with TPlayer(Player) do begin
643 Inc(TotalCells);
644 if Assigned(OneUnit) then
645 Inc(TotalUnits, OneUnit.Power);
646 if Assigned(Building) and Assigned(Building.Kind) and
647 (Building.Kind.SpecialType = stCity) then
648 Inc(TotalCities);
649 if Extra = etObjectiveTarget then
650 Inc(TotalWinObjectiveCells);
651 end;
652 end;
653end;
654
655procedure TMap.Generate;
656var
657 X, Y: Integer;
658 NewCell: TCell;
659begin
660 Clear;
661
662 // Allocate and init new
663 Cells.Count := FSize.Y * FSize.X;
664 FNewCellId := 1;
665 for Y := 0 to FSize.Y - 1 do
666 for X := 0 to FSize.X - 1 do begin
667 NewCell := TCell.Create;
668 NewCell.Map := Self;
669 NewCell.PosPx := TPoint.Create(X * DefaultCellSize.X, Y * DefaultCellSize.Y);
670 NewCell.Id := GetNewCellId;
671 SetLength(NewCell.Polygon.Points, 1);
672 NewCell.Polygon.Points[0] := NewCell.PosPx;
673 Cells[Y * FSize.X + X] := NewCell;
674 end;
675 FPixelRect := FPixelRect;
676end;
677
678procedure TMap.MakeSymetric;
679var
680 C: Integer;
681 I: Integer;
682 CellLink: TCellLink;
683 OtherCell1: TCell;
684 OtherCell2: TCell;
685 Cell: TCell;
686 OppositeCell: TCell;
687begin
688 // Generic way to create two sides symetric map independent to shape
689 // Set first half as inverted second half
690 for C := 0 to (Cells.Count div 2) - 1 do begin
691 Cell := Cells[C];
692 OppositeCell := Cells[Cells.Count - 1 - C];
693
694 Cell.Terrain := OppositeCell.Terrain;
695
696 // Sync units
697 if not Assigned(Cell.OneUnit) and Assigned(OppositeCell.OneUnit) then begin
698 Cell.OneUnit := TGame(Game).Units.AddNew(OppositeCell.OneUnit.Kind,
699 OppositeCell.OneUnit.Power);
700 end;
701 if Assigned(Cell.OneUnit) and Assigned(OppositeCell.OneUnit) then begin
702 Cell.OneUnit.Kind := OppositeCell.OneUnit.Kind;
703 Cell.OneUnit.Power := OppositeCell.OneUnit.Power;
704 end;
705
706 if Assigned(Cell.OneUnit) and not Assigned(OppositeCell.OneUnit) then begin
707 TGame(Game).Units.Remove(Cell.OneUnit);
708 end;
709
710 for I := Cell.Links.Count - 1 downto 0 do begin
711 CellLink := Cell.Links[I];
712
713 // Remove cells on first half of the map
714 if (Cells.IndexOf(CellLink.Cells[0]) <= (Cells.Count div 2)) and
715 (Cells.IndexOf(CellLink.Cells[1]) <= (Cells.Count div 2)) then
716 begin
717 CellLinks.Remove(CellLink);
718 Continue;
719 end;
720
721 // Make cross half links symetric
722 if (Cells.IndexOf(CellLink.Cells[0]) <= (Cells.Count div 2)) and
723 (Cells.IndexOf(CellLink.Cells[1]) >= (Cells.Count div 2)) then begin
724 OtherCell1 := Cells[Cells.Count - 1 - Cells.IndexOf(CellLink.Cells[1])];
725 OtherCell2 := CellLink.Cells[1];
726 CellLinks.Remove(CellLink);
727 if not Assigned(CellLinks.FindByCells(OtherCell1, OtherCell2)) then
728 CellLinks.AddLink(OtherCell1, OtherCell2);
729 end else
730 if (Cells.IndexOf(CellLink.Cells[0]) >= (Cells.Count div 2)) and
731 (Cells.IndexOf(CellLink.Cells[1]) <= (Cells.Count div 2)) then begin
732 OtherCell1 := Cells[Cells.Count - 1 - Cells.IndexOf(CellLink.Cells[0])];
733 OtherCell2 := CellLink.Cells[0];
734 CellLinks.Remove(CellLink);
735 if not Assigned(CellLinks.FindByCells(OtherCell1, OtherCell2)) then
736 CellLinks.AddLink(OtherCell1, OtherCell2);
737 end;
738 end;
739 end;
740
741 for C := 0 to (Cells.Count div 2) - 1 do begin
742 // Make copy of links from second half
743 OppositeCell := Cells[Cells.Count - 1 - C];
744
745 for CellLink in OppositeCell.Links do
746 if (Cells.IndexOf(CellLink.Cells[0]) > (Cells.Count div 2)) and
747 (Cells.IndexOf(CellLink.Cells[1]) > (Cells.Count div 2)) then begin
748 OtherCell1 := Cells[Cells.Count - 1 - Cells.IndexOf(CellLink.Cells[0])];
749 OtherCell2 := Cells[Cells.Count - 1 - Cells.IndexOf(CellLink.Cells[1])];
750 if not Assigned(CellLinks.FindByCells(OtherCell1, OtherCell2)) then
751 CellLinks.AddLink(OtherCell1, OtherCell2);
752 end;
753 end;
754end;
755
756procedure TMap.CreateLinks;
757var
758 LastAreaCount: Integer;
759begin
760 BuildMapAreas;
761 LastAreaCount := -1;
762 while (Areas.Count > 1) and (Areas.Count <> LastAreaCount) do begin
763 LastAreaCount := Areas.Count;
764 BuildBridges;
765 BuildMapAreas;
766 end;
767end;
768
769procedure TMap.Clear;
770begin
771 CellLinks.Clear;
772 Cells.Clear;
773 FNewCellId := 1;
774 FPixelRect.SetEmpty;
775end;
776
777procedure TMap.CheckCells;
778var
779 I: Integer;
780 J: Integer;
781begin
782 for I := 0 to Cells.Count - 1 do begin
783 for J := I + 1 to Cells.Count - 1 do begin
784 if (Cells[I].Id = Cells[J].Id) then
785 raise Exception.Create('Duplicate cells ID ' + IntToStr(I) + ' ' + IntToStr(J));
786 if (Cells[I].PosPx = Cells[J].PosPx) then
787 raise Exception.Create('Duplicate cells position ' + IntToStr(I) + ' ' + IntToStr(J));
788 end;
789 end;
790end;
791
792function TMap.ToString: string;
793begin
794 Result := 'MaxPower: ' + IntToStr(MaxPower) + LineEnding;
795 Result := Result + 'Cyclic: ' + BoolToStr(Cyclic) + LineEnding;
796 Result := Result + 'Size: ' + IntToStr(Size.X) + 'x' + IntToStr(Size.Y) + LineEnding;
797 Result := Result + 'DefaultCellSize: ' + IntToStr(DefaultCellSize.X) + 'x' + IntToStr(DefaultCellSize.Y) + LineEnding;
798 Result := Result + 'Shape: ' + IntToStr(Integer(Shape)) + LineEnding;
799 Result := Result + Cells.ToString + LineEnding;
800end;
801
802constructor TMap.Create;
803begin
804 MaxPower := DefaultMaxPower;
805 DefaultCellSize := TPoint.Create(220, 220);
806 Cells := TCells.Create;
807 Cells.Map := Self;
808 Size := TPoint.Create(0, 0);
809 Image := TImage.Create(nil);
810 CellLinks := TCellLinks.Create;
811 CellLinks.Map := Self;
812 Areas := TMapAreas.Create;
813end;
814
815destructor TMap.Destroy;
816begin
817 Size := TPoint.Create(0, 0);
818 FreeAndNil(Areas);
819 FreeAndNil(CellLinks);
820 FreeAndNil(Image);
821 FreeAndNil(Cells);
822 inherited;
823end;
824
825function TMap.CalculatePixelRect: TRect;
826var
827 I: Integer;
828 CellRect: TRect;
829begin
830 Result := TRect.Create(TPoint.Create(0, 0), TPoint.Create(0, 0));
831 // This is generic algorithm to determine pixel size of entire map
832 for I := 0 to Cells.Count - 1 do begin
833 CellRect := Cells[I].Polygon.GetRect;
834 if I = 0 then Result := CellRect
835 else begin
836 Result.P1 := TPoint.Min(Result.P1, CellRect.P1);
837 Result.P2 := TPoint.Max(Result.P2, CellRect.P2);
838 end;
839 end;
840end;
841
842procedure TMap.ForEachCells(Method: TMethod);
843begin
844end;
845
846function TMap.SearchDifferentCellArea(List: TCells; SourceArea, DestArea: TMapArea): TCell;
847var
848 NewList: TCells;
849 NewListVoid: TCells;
850 I: Integer;
851 C: Integer;
852begin
853 Result := nil;
854 NewList := TCells.Create;
855 NewList.OwnsObjects := False;
856 NewListVoid := TCells.Create;
857 NewListVoid.OwnsObjects := False;
858
859 for C := 0 to List.Count - 1 do
860 with List[C] do begin
861 for I := 0 to Neighbors.Count - 1 do
862 with Neighbors[I] do
863 if (not Mark) and (Terrain <> ttVoid) and (Area <> SourceArea) and ((DestArea = nil) or (DestArea = Area)) then begin
864 NewList.Add(List[C].Neighbors[I]);
865 Mark := True;
866 end else
867 if (not Mark) and (Terrain = ttVoid) then begin
868 NewListVoid.Add(List[C].Neighbors[I]);
869 Mark := True;
870 end;
871 end;
872
873 if NewList.Count > 0 then begin
874 // We found cell with different area
875 Result := NewList[Random(NewList.Count)];
876 end else
877 if NewListVoid.Count > 0 then begin
878 // Cell was not found but we have more void cells to check
879 Result := SearchDifferentCellArea(NewListVoid, SourceArea, DestArea);
880 end;
881
882 FreeAndNil(NewListVoid);
883 FreeAndNil(NewList);
884end;
885
886procedure TMap.BuildBridges;
887var
888 List: TCells;
889 BorderList: TCells;
890 Cell: TCell;
891 FoundCell1: TCell;
892 FoundCell2: TCell;
893 I: Integer;
894 J: Integer;
895begin
896 List := TCells.Create;
897 List.OwnsObjects := False;
898
899 BorderList := TCells.Create;
900 BorderList.OwnsObjects := False;
901
902 // Build area bridges
903 if Areas.Count > 1 then
904 for I := 0 to Areas.Count - 1 do
905 with Areas[I] do begin
906 GetBorderCells(BorderList);
907 if BorderList.Count > 0 then
908 for J := 0 to 4 do begin
909
910 Cell := BorderList[Random(BorderList.Count)];
911 List.Clear;
912 List.Add(Cell);
913
914 Map.Cells.ClearMark;
915
916 // Find nearest cell with different area
917 FoundCell1 := SearchDifferentCellArea(List, TMapArea(Map.Areas[I]), nil);
918 if Assigned(FoundCell1) then begin
919 // Again find back nearest cell with different area.
920 // This will ensure that both cells are closest ones
921
922 Map.Cells.ClearMark;
923 List[0] := FoundCell1;
924 FoundCell2 := SearchDifferentCellArea(List, FoundCell1.Area, TMapArea(Map.Areas[I]));
925 if Assigned(FoundCell2) then begin
926 // Check if link doesn't exist already
927 if not Assigned(FoundCell1.Links.FindByCells(FoundCell1, FoundCell2)) then begin
928 Map.CellLinks.AddLink(FoundCell1, FoundCell2);
929 Inc(BridgeCount);
930 end;
931 end;
932 end;
933 end;
934 end;
935 FreeAndNil(List);
936 FreeAndNil(BorderList);
937end;
938
939procedure TMap.BuildMapAreas;
940var
941 C: Integer;
942 NewArea: TMapArea;
943begin
944 for C := 0 to Cells.Count - 1 do
945 with Cells[C] do
946 Area := nil;
947 Areas.Clear;
948 for C := 0 to Cells.Count - 1 do
949 with Cells[C] do
950 if (Terrain <> ttVoid) and (not Assigned(Area)) then begin
951 NewArea := TMapArea.Create;
952 NewArea.Id := Map.Areas.Count;
953 NewArea.Map := Map;
954 Areas.Add(NewArea);
955 Area := NewArea;
956 AreaExtend;
957 end;
958end;
959
960{ TCell }
961
962procedure TCell.SetUnit(AValue: TUnit);
963var
964 OldValue: TUnit;
965begin
966 if FUnit = AValue then Exit;
967 OldValue := FUnit;
968 FUnit := nil;
969 if Assigned(OldValue) then OldValue.MapCell := nil;
970 FUnit := AValue;
971 if Assigned(FUnit) then FUnit.MapCell := Self;
972end;
973
974function TCell.Compare(Cell: TCell): Boolean;
975begin
976 Result := (Id = Cell.Id) and
977 (PosPx = Cell.PosPx) and
978 (Terrain = Cell.Terrain) and
979 Polygon.Compare(Cell.Polygon) and
980 (Player = Cell.Player) and
981 (Mark = Cell.Mark) and
982 (Extra = Cell.Extra);
983end;
984
985procedure TCell.ConnectTo(Cell: TCell);
986begin
987 if Cell = Self then
988 raise Exception.Create('Can''t connect map cell to itself');
989 // Connect only if already not connected
990 if Neighbors.IndexOf(Cell) < 0 then begin
991 Cell.Neighbors.Add(Self);
992 Neighbors.Add(Cell);
993 end;
994end;
995
996procedure TCell.DisconnectFrom(Cell: TCell);
997var
998 I: Integer;
999begin
1000 I := Cell.Neighbors.IndexOf(Self);
1001 if I >= 0 then Cell.Neighbors.Delete(I) else
1002 raise Exception.Create('Can''t disconnect neigboring cells.');
1003 I := Neighbors.IndexOf(Cell);
1004 if I >= 0 then Neighbors.Delete(I)
1005 else Exception.Create('Can''t disconnect neigboring cells.');
1006end;
1007
1008function TCell.NeighboringToVoid: Boolean;
1009var
1010 NeighVoidCount: Integer;
1011 NeighborCell: TCell;
1012begin
1013 NeighVoidCount := 0;
1014 for NeighborCell in Neighbors do
1015 if (NeighborCell.Terrain = ttVoid) then Inc(NeighVoidCount);
1016 Result := NeighVoidCount > 0;
1017end;
1018
1019procedure TCell.SetArea(AValue: TMapArea);
1020begin
1021 if FArea = AValue then Exit;
1022 if Assigned(FArea) then FArea.Cells.Remove(Self);
1023 FArea := AValue;
1024 if Assigned(FArea) then FArea.Cells.Add(Self);
1025end;
1026
1027procedure TCell.SetBuilding(AValue: TBuilding);
1028var
1029 OldValue: TBuilding;
1030begin
1031 if FBuilding = AValue then Exit;
1032 OldValue := FBuilding;
1033 FBuilding := nil;
1034 if Assigned(OldValue) then OldValue.MapCell := nil;
1035 FBuilding := AValue;
1036 if Assigned(FBuilding) then FBuilding.MapCell := Self;
1037end;
1038
1039procedure TCell.SetId(AValue: Integer);
1040begin
1041 if FId = AValue then Exit;
1042 FId := AValue;
1043end;
1044
1045procedure TCell.AreaExtend;
1046var
1047 NeighborCell: TCell;
1048begin
1049 for NeighborCell in Neighbors do
1050 if (NeighborCell.Terrain <> ttVoid) and (not Assigned(NeighborCell.Area)) then begin
1051 NeighborCell.Area := Area;
1052 NeighborCell.AreaExtend;
1053 end;
1054end;
1055
1056procedure TCell.FixRefId;
1057var
1058 I: Integer;
1059 Cell: TCell;
1060begin
1061 if PlayerId <> 0 then begin
1062 Player := TGame(Map.Game).Players.FindById(PlayerId);
1063 if not Assigned(Player) then
1064 raise Exception.Create('Referenced player id ' + IntToStr(PlayerId) + ' not found.');
1065 end else Player := nil;
1066
1067 if BuildingId <> 0 then begin
1068 Building := TGame(Map.Game).Buildings.FindById(BuildingId);
1069 if not Assigned(Building) then
1070 raise Exception.Create('Referenced building id ' + IntToStr(BuildingId) + ' not found.');
1071 end else Building := nil;
1072
1073 if OneUnitId <> 0 then begin
1074 OneUnit := TGame(Map.Game).Units.FindById(OneUnitId);
1075 if not Assigned(OneUnit) then
1076 raise Exception.Create('Referenced unit id ' + IntToStr(OneUnitId) + ' not found.');
1077 end else OneUnit := nil;
1078
1079 Neighbors.Count := Length(NeighborsId);
1080 for I := 0 to Length(NeighborsId) - 1 do
1081 Neighbors[I] := nil;
1082 for I := 0 to Length(NeighborsId) - 1 do begin
1083 Cell := Map.Cells.FindById(NeighborsId[I]);
1084 if not Assigned(Cell) then
1085 raise Exception.Create('Neighbor cell id not found ' + IntToStr(NeighborsId[I]));
1086 if Neighbors.IndexOf(Cell) <> -1 then
1087 raise Exception.Create('Duplicate neighbor cell ' + IntToStr(NeighborsId[I]) + ' found for cell ' + IntToStr(Id));
1088 Neighbors[I] := Cell;
1089 end;
1090end;
1091
1092procedure TCell.LoadFromNode(Node: TDOMNode);
1093var
1094 Node2: TDOMNode;
1095 Node3: TDOMNode;
1096begin
1097 Id := ReadInteger(Node, 'Id', 0);
1098 OneUnitId := ReadInteger(Node, 'Unit', 0);
1099 Terrain := TTerrainType(ReadInteger(Node, 'Terrain', Integer(ttVoid)));
1100 Extra := TExtraType(ReadInteger(Node, 'Extra', Integer(etNone)));
1101 PosPx.X := ReadInteger(Node, 'PosX', 0);
1102 PosPx.Y := ReadInteger(Node, 'PosY', 0);
1103 PlayerId := ReadInteger(Node, 'Player', 0);
1104 BuildingId := ReadInteger(Node, 'Building', 0);
1105
1106 Node3 := Node.FindNode('Neighbours');
1107 if Assigned(Node3) then begin
1108 SetLength(NeighborsId, 0);
1109 Node2 := Node3.FirstChild;
1110 while Assigned(Node2) and (Node2.NodeName = 'Neighbour') do begin
1111 SetLength(NeighborsId, Length(NeighborsId) + 1);
1112 NeighborsId[High(NeighborsId)] := ReadInteger(Node2, 'Id', 0);
1113 Node2 := Node2.NextSibling;
1114 end;
1115 end;
1116
1117 Node3 := Node.FindNode('Polygon');
1118 if Assigned(Node3) then begin
1119 Polygon.Clear;
1120 Node2 := Node3.FirstChild;
1121 while Assigned(Node2) and (Node2.NodeName = 'Point') do begin
1122 Polygon.AddPoint(TPoint.Create(ReadInteger(Node2, 'X', 0), ReadInteger(Node2, 'Y', 0)));
1123 Node2 := Node2.NextSibling;
1124 end;
1125 end;
1126end;
1127
1128procedure TCell.SaveToNode(Node: TDOMNode);
1129var
1130 NewNode: TDOMNode;
1131 NewNode2: TDOMNode;
1132 I: Integer;
1133begin
1134 WriteInteger(Node, 'Id', Id);
1135 if Assigned(OneUnit) then
1136 WriteInteger(Node, 'Unit', OneUnit.Id)
1137 else WriteInteger(Node, 'Unit', 0);
1138 WriteInteger(Node, 'Terrain', Integer(Terrain));
1139 WriteInteger(Node, 'Extra', Integer(Extra));
1140 WriteInteger(Node, 'PosX', PosPx.X);
1141 WriteInteger(Node, 'PosY', PosPx.Y);
1142 if Assigned(Player) then
1143 WriteInteger(Node, 'Player', TPlayer(Player).Id)
1144 else WriteInteger(Node, 'Player', 0);
1145 if Assigned(Building) then
1146 WriteInteger(Node, 'Building', Building.Id)
1147 else WriteInteger(Node, 'Building', 0);
1148 NewNode := Node.OwnerDocument.CreateElement('Neighbours');
1149 Node.AppendChild(NewNode);
1150 for I := 0 to Neighbors.Count - 1 do begin
1151 NewNode2 := NewNode.OwnerDocument.CreateElement('Neighbour');
1152 NewNode.AppendChild(NewNode2);
1153 WriteInteger(NewNode2, 'Id', Neighbors[I].Id);
1154 end;
1155 NewNode := Node.OwnerDocument.CreateElement('Polygon');
1156 Node.AppendChild(NewNode);
1157 for I := 0 to Length(Polygon.Points) - 1 do begin
1158 NewNode2 := NewNode.OwnerDocument.CreateElement('Point');
1159 NewNode.AppendChild(NewNode2);
1160 WriteInteger(NewNode2, 'X', Polygon.Points[I].X);
1161 WriteInteger(NewNode2, 'Y', Polygon.Points[I].Y);
1162 end;
1163end;
1164
1165procedure TCell.Assign(Source: TCell);
1166begin
1167 Id := Source.Id;
1168 PosPx := Source.PosPx;
1169 Terrain := Source.Terrain;
1170 Polygon := Source.Polygon;
1171 Player := Source.Player;
1172 Mark := Source.Mark;
1173 Extra := Source.Extra;
1174 // TODO: How to copy neighbours and moves list
1175end;
1176
1177function TCell.GetColor: TColor;
1178begin
1179 if Assigned(Player) then Result := TPlayer(Player).Color
1180 else Result := clGray;
1181end;
1182
1183function TCell.ToString: ansistring;
1184begin
1185 Result := 'Id: ' + IntToStr(Id) + LineEnding;
1186 Result := Result + 'PosPx: ' + IntToStr(PosPx.X) + 'x' + IntToStr(PosPx.Y) + LineEnding;
1187 Result := Result + 'Terrain: ' + IntToStr(Integer(Terrain)) + LineEnding;
1188 Result := Result + 'Extra: ' + IntToStr(Integer(Extra)) + LineEnding;
1189 //Result := Result + 'Polygon: ' + IntToStr(Polygon) + LineEnding;
1190 if Assigned(Player) then
1191 Result := Result + 'Player: ' + IntToStr(TPlayer(Player).Id) + LineEnding;
1192 Result := Result + 'Mark:' + BoolToStr(Mark) + LineEnding;
1193 if Assigned(OneUnit) then
1194 Result := Result + 'OneUnit:' + IntToStr(OneUnit.Id) + LineEnding;
1195end;
1196
1197procedure TCell.CheckOwnership;
1198begin
1199 if TGame(Map.Game).GameSystem.EmptyCellsNeutral then
1200 if Assigned(Player) and (Assigned(OneUnit) and (OneUnit.Power = 0)) or
1201 not Assigned(OneUnit) then
1202 Player := nil;
1203end;
1204
1205constructor TCell.Create;
1206begin
1207 FId := -1;
1208 Player := nil;
1209 Neighbors := TCells.Create;
1210 Neighbors.OwnsObjects := False;
1211 Links := TCellLinks.Create;
1212 Links.OwnsObjects := False;
1213end;
1214
1215destructor TCell.Destroy;
1216var
1217 I: Integer;
1218begin
1219 OneUnit := nil;
1220 for I := Links.Count - 1 downto 0 do
1221 FMap.CellLinks.Remove(Links[I]);
1222 FreeAndNil(Links);
1223 for I := Neighbors.Count - 1 downto 0 do
1224 if Neighbors[I].Neighbors.Remove(Self) = -1 then
1225 raise Exception.Create(SCellRemoveNeighborError);
1226 FreeAndNil(Neighbors);
1227 inherited;
1228end;
1229
1230end.
1231
Note: See TracBrowser for help on using the repository browser.