source: trunk/Map.pas

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