source: tags/1.0.0/UEngine.pas

Last change on this file was 44, checked in by chronos, 6 years ago
  • Fixed: Removed compilation warnings.
  • Modified: Preparation for 1.0.0 version release.
File size: 80.9 KB
Line 
1unit UEngine;
2
3{$mode delphi}
4
5interface
6
7uses
8 Classes, SysUtils, Contnrs, Graphics, Controls, ExtCtrls, Math, DateUtils,
9 UMetaCanvas;
10
11type
12 TStationShape = (ssCircle, ssSquare, ssTriangle, ssStar, ssPlus, ssPentagon,
13 ssDiamond, ssQuarterCircle, ssHexagon, ssCross, ssHalfCircle, ssHeptagon);
14 TStationShapeSet = set of TStationShape;
15 TEngine = class;
16 TMetroPassengers = class;
17 TMetroLines = class;
18 TMetroLine = class;
19 TMetroTrains = class;
20 TTrackPoint = class;
21 TLineStation = class;
22 TTrackPoints = class;
23 TTrack = class;
24 TTrackLink = class;
25 TTrackLinks = class;
26
27 { TMapStation }
28
29 TMapStation = class
30 private
31 procedure ShiftTrackPoints;
32 procedure SortLines;
33 public
34 Engine: TEngine;
35 Shape: TStationShape;
36 Position: TPoint;
37 Passengers: TMetroPassengers;
38 Lines: TMetroLines;
39 ShapeDistance: array[TStationShape] of Integer;
40 OverloadDuration: TDateTime;
41 function IsBestStationForShape(Shape: TStationShape; Check, Current: TLineStation): Boolean;
42 constructor Create;
43 destructor Destroy; override;
44 end;
45
46 { TMapStations }
47
48 TMapStations = class(TObjectList)
49 Engine: TEngine;
50 function GetRect: TRect;
51 function AddNew: TMapStation;
52 end;
53
54 TLineStation = class
55 Line: TMetroLine;
56 MapStation: TMapStation;
57 TrackPoint: TTrackPoint;
58 end;
59
60 { TLineStations }
61
62 TLineStations = class(TObjectList)
63 Line: TMetroLine;
64 function SearchMapStation(Station: TMapStation): TLineStation;
65 end;
66
67 { TTrackPoint }
68
69 TTrackPoint = class
70 LineStation: TLineStation;
71 Position: TPoint;
72 //PositionShift: TPoint;
73 PositionDesigned: TPoint;
74 Pending: Boolean;
75 Track: TTrack;
76 NeighPoints: TTrackPoints;
77 NeighLinks: TTrackLinks;
78 LinkUp: TTrackLink;
79 LinkDown: TTrackLink;
80 procedure Connect(TrackPoint: TTrackPoint);
81 procedure Disconnect(TrackPoint: TTrackPoint);
82 function GetDown: TTrackPoint;
83 function GetUp: TTrackPoint;
84 function GetNeighDown: TTrackPoint;
85 function GetNeighUp: TTrackPoint;
86 function GetLinkDown: TTrackLink;
87 function GetLinkUp: TTrackLink;
88 // Move to TTrackLink later
89 function GetDistance: Integer;
90 constructor Create;
91 destructor Destroy; override;
92 end;
93
94 { TTrackPoints }
95
96 TTrackPoints = class(TObjectList)
97 Track: TTrack;
98 function AddNew: TTrackPoint;
99 end;
100
101 { TTrackLink }
102
103 TTrackLink = class
104 Points: TTrackPoints;
105 Shift: TPoint;
106 Track: TTrack;
107 constructor Create;
108 destructor Destroy; override;
109 end;
110
111 { TTrackLinks }
112
113 TTrackLinks = class(TObjectList)
114 Track: TTrack;
115 function SearchPoints(Point1, Point2: TTrackPoint): TTrackLink;
116 function AddNew: TTrackLink;
117 end;
118
119 TTrack = class
120 Points: TTrackPoints;
121 Links: TTrackLinks;
122 Line: TMetroLine;
123 procedure RouteTrack(TP1, TP2: TTrackPoint);
124 procedure RemoveTrackBetween(TP1, TP2: TTrackPoint);
125 constructor Create;
126 destructor Destroy; override;
127 end;
128
129 { TTracks }
130
131 TTracks = class(TObjectList)
132 function SearchPointUp(TrackPoint: TTrackPoint; Skip: TTrackLink): TTrackLink;
133 function SearchPointDown(TrackPoint: TTrackPoint; Skip: TTrackLink): TTrackLink;
134 end;
135
136 { TTrackPointsAngle }
137
138 TTrackPointsAngle = class
139 Angle: Double;
140 TrackLinks: TTrackLinks;
141 constructor Create;
142 destructor Destroy; override;
143 end;
144
145 { TTrackPointsAngleGroup }
146
147 TTrackPointsAngleGroup = class(TObjectList)
148 function SearchAngle(Angle: Double): TTrackPointsAngle;
149 end;
150
151 { TMetroLine }
152
153 TMetroLine = class
154 private
155 procedure UpdateEndingLines;
156 public
157 Index: Integer;
158 Engine: TEngine;
159 Color: TColor;
160 LineStations: TLineStations;
161 Trains: TMetroTrains;
162 Track: TTrack;
163 procedure ConnectStation(Station: TMapStation; LineStationDown, LineStationUp: TLineStation);
164 procedure DisconnectStation(ALineStation: TLineStation);
165 function GetTrackLength: Integer;
166 constructor Create;
167 destructor Destroy; override;
168 function IsCircular: Boolean;
169 end;
170
171 { TMetroLines }
172
173 TMetroLines = class(TObjectList)
174 Engine: TEngine;
175 function AddNew: TMetroLine;
176 function SearchByColor(Color: TColor): TMetroLine;
177 end;
178
179 { TMetroTrain }
180
181 TMetroTrain = class
182 private
183 FLine: TMetroLine;
184 LastPosDelta: Integer;
185 LastTrainMoveTime: TDateTime;
186 StationStopTime: TDateTime;
187 procedure SetLine(AValue: TMetroLine);
188 public
189 Passengers: TMetroPassengers;
190
191 BaseTrackPoint: TTrackPoint;
192 RelPos: Double;
193 Direction: Integer;
194 InStation: Boolean;
195 TargetStation: TLineStation;
196 function GetTargetStationDistance: Integer;
197 function GetPosition: TPoint;
198 function GetAngle: Double;
199 constructor Create;
200 destructor Destroy; override;
201 property Line: TMetroLine read FLine write SetLine;
202 end;
203
204 { TMetroTrains }
205
206 TMetroTrains = class(TObjectList)
207 function GetUnusedTrain: TMetroTrain;
208 function GetUnusedCount: Integer;
209 function AddNew: TMetroTrain;
210 end;
211
212 TMetroPassenger = class
213 Engine: TEngine;
214 Shape: TStationShape;
215 Station: TMapStation;
216 Train: TMetroTrain;
217 end;
218
219 { TMetroPassengers }
220
221 TMetroPassengers = class(TObjectList)
222 Engine: TEngine;
223 function AddNew: TMetroPassenger;
224 end;
225
226 { TRiver }
227
228 TRiver = class
229 Points: array of TPoint;
230 procedure Paint(Canvas: TCanvas);
231 end;
232
233 TRivers = class(TObjectList)
234 end;
235
236 TMap = class
237 Size: TPoint;
238 Rivers: TRivers;
239 constructor Create;
240 destructor Destroy; override;
241 end;
242
243 { TView }
244
245 TView = class
246 private
247 FDestRect: TRect;
248 FSourceRect: TRect;
249 FZoom: Double;
250 procedure SetDestRect(AValue: TRect);
251 procedure SetSourceRect(AValue: TRect);
252 procedure SetZoom(AValue: Double);
253 public
254 function PointDestToSrc(Pos: TPoint): TPoint;
255 function PointSrcToDest(Pos: TPoint): TPoint;
256 constructor Create;
257 property SourceRect: TRect read FSourceRect write SetSourceRect;
258 property DestRect: TRect read FDestRect write SetDestRect;
259 property Zoom: Double read FZoom write SetZoom;
260 end;
261
262 TGameState = (gsNotStarted, gsRunning, gsPaused, gsGameOver);
263
264 { TEngine }
265
266 TEngine = class
267 private
268 LastMousePos: TPoint;
269 LastFocusedStation: TMapStation;
270 MouseHold: Boolean;
271 LastNewStationTime: TDateTime;
272 LastNewPassengerTime: TDateTime;
273 LastNewWeekTime: TDateTime;
274 LastNewShapeTime: TDateTime;
275 LastTickTime: TDateTime;
276 FTime: TDateTime;
277 FLastTime: TDateTime;
278 MetaCanvas: TMetaCanvas;
279 procedure ResizeView;
280 function GetExistStationShapes: TStationShapeSet;
281 function GetStationOnPos(Pos: TPoint): TMapStation;
282 function GetTrackOnPos(Pos: TPoint): TTrackLink;
283 function GetTrainOnPos(Pos: TPoint): TMetroTrain;
284 procedure DrawLine(Canvas: TCanvas; Pos: TPoint);
285 procedure DrawShape(Canvas: TCanvas; Position: TPoint; Shape: TStationShape;
286 Size: Integer; Angle: Double);
287 procedure DrawClock(Canvas: TCanvas);
288 procedure DrawTrains(Canvas: TCanvas);
289 procedure ComputeShapeDistance;
290 procedure ComputeShapeDistanceStation(Station: TMapStation;
291 UpdatedShape: TStationShape; Distance: Integer);
292 procedure TrainMovement;
293 function GetUnusedLine: TMetroLine;
294 procedure ShiftTrackPoints;
295 public
296 Passengers: TMetroPassengers;
297 Stations: TMapStations;
298 Lines: TMetroLines;
299 Trains: TMetroTrains;
300 ShapeCount: Integer;
301 Map: TMap;
302 View: TView;
303 SelectedLine: TMetroLine;
304 SelectedTrain: TMetroTrain;
305 TrackStationDown: TTrackPoint;
306 TrackStationUp: TTrackPoint;
307 ServedPassengerCount: Integer;
308 State: TGameState;
309 RedrawPending: Boolean;
310 ImagePassenger: TImage;
311 ImageLocomotive: TImage;
312 procedure MouseMove(Position: TPoint);
313 procedure MouseUp(Button: TMouseButton; Position: TPoint);
314 procedure MouseDown(Button: TMouseButton; Position: TPoint);
315 procedure Reset;
316 procedure Redraw;
317 constructor Create;
318 destructor Destroy; override;
319 procedure Tick;
320 procedure Paint(TargetCanvas: TCanvas);
321 property Time: TDateTime read FTime;
322 end;
323
324const
325 clDarkYellow = TColor($00dede);
326 clOrange = TColor($0080ff);
327 clBrown = TColor($003090);
328 clCyan = TColor($FFFF00);
329 LineColors: array[0..8] of TColor = (clBlue, clRed, clDarkYellow, clGreen,
330 clPurple, clGray, clOrange, clBrown, clCyan);
331 StationSize = 30;
332 StationOverloadSize = 60;
333 PassengerSize = 15;
334 TrainSize = 40;
335 LineColorsDist = 50;
336 TrainSpeed = 2000;
337 ImagePassengerName = 'Images/Passenger.png';
338 ImageLocomotiveName = 'Images/Locomotive.png';
339 TrainPassengerCount = 6;
340 StationMinDistance = 100;
341 StationMaxDistance = 300;
342 MaxWaitingPassengers = 10;
343 MaxPassengersOveloadTime = 2;
344 MetroLineThickness = 13;
345 TrackClickDistance = 20;
346 EndStationLength = 50;
347 ShowDistances = False;
348 //TimePerSecond = (60 * OneMinute);
349 TimePerSecond = (60 * OneMinute);
350 NewStationPeriod = 1;
351 NewShapePeriod = 10;
352 NewTrainPeriod = 7; // Each week
353 NewPassengerPeriod = 0.3 * OneSecond;
354 NewPassengerProbability = 0.003;
355 VisiblePassengersPerLine = 6;
356
357implementation
358
359uses
360 UGeometric;
361
362resourcestring
363 SZeroZoomNotAlowed = 'Zero zoom not allowed';
364
365{ TTrackLinks }
366
367function TTrackLinks.SearchPoints(Point1, Point2: TTrackPoint): TTrackLink;
368var
369 I: Integer;
370begin
371 I := 0;
372 while (I < 0) and
373 ((TTrackLink(Items[I]).Points.First <> Point1) or (TTrackLink(Items[I]).Points.Last <> Point2))
374 and ((TTrackLink(Items[I]).Points.First <> Point2) or (TTrackLink(Items[I]).Points.Last <> Point1)) do
375 Inc(I);
376 if I < 0 then Result := TTrackLink(Items[I])
377 else Result := nil;
378end;
379
380function TTrackLinks.AddNew: TTrackLink;
381begin
382 Result := TTrackLink.Create;
383 Result.Track := Track;
384end;
385
386{ TTrackPoints }
387
388function TTrackPoints.AddNew: TTrackPoint;
389begin
390 Result := TTrackPoint.Create;
391 Result.Track := Track;
392end;
393
394{ TTrack }
395
396constructor TTrack.Create;
397begin
398 Points := TTrackPoints.Create;
399 Points.Track := Self;
400 Links := TTrackLinks.Create;
401 Links.Track := Self;
402end;
403
404destructor TTrack.Destroy;
405begin
406 Points.Free;
407 Links.Free;
408 inherited Destroy;
409end;
410
411{ TTrackLink }
412
413constructor TTrackLink.Create;
414begin
415 Points := TTrackPoints.Create;
416 Points.OwnsObjects := False;
417end;
418
419destructor TTrackLink.Destroy;
420begin
421 Points.Free;
422 inherited Destroy;
423end;
424
425{ TRiver }
426
427procedure TRiver.Paint(Canvas: TCanvas);
428begin
429 Canvas.Brush.Color := $ffffe0;
430 Canvas.Brush.Style := bsSolid;
431 Canvas.Polygon(Points);
432end;
433
434{ TMap }
435
436constructor TMap.Create;
437begin
438 Rivers := TRivers.Create;
439end;
440
441destructor TMap.Destroy;
442begin
443 Rivers.Free;
444 inherited Destroy;
445end;
446
447{ TView }
448
449procedure TView.SetDestRect(AValue: TRect);
450var
451 Diff: TPoint;
452begin
453 if RectEquals(FDestRect, AValue) then Exit;
454 Diff := Point(Trunc((DestRect.Right - DestRect.Left) / Zoom - (AValue.Right - AValue.Left) / Zoom) div 2,
455 Trunc((DestRect.Bottom - DestRect.Top) / Zoom - (AValue.Bottom - AValue.Top) / Zoom) div 2);
456 FDestRect := AValue;
457 FSourceRect := Bounds(FSourceRect.Left + Diff.X, FSourceRect.Top + Diff.Y,
458 Trunc((DestRect.Right - DestRect.Left) / Zoom),
459 Trunc((DestRect.Bottom - DestRect.Top) / Zoom));
460end;
461
462procedure TView.SetSourceRect(AValue: TRect);
463var
464 ZX: Double;
465 ZY: Double;
466begin
467 if RectEquals(FSourceRect, AValue) then Exit;
468 FSourceRect := AValue;
469 ZX := (FDestRect.Right - FDestRect.Left) / (FSourceRect.Right - FSourceRect.Left);
470 ZY := (FDestRect.Bottom - FDestRect.Top) / (FSourceRect.Bottom - FSourceRect.Top);
471 if ZX > ZY then
472 Zoom := ZY
473 else Zoom := ZX;
474end;
475
476procedure TView.SetZoom(AValue: Double);
477begin
478 if FZoom = AValue then Exit;
479 if AValue = 0 then
480 raise Exception.Create(SZeroZoomNotAlowed);
481 FZoom := AValue;
482 FSourceRect := Bounds(Trunc(FSourceRect.Left + (FSourceRect.Right - FSourceRect.Left) div 2 - (DestRect.Right - DestRect.Left) / Zoom / 2),
483 Trunc(FSourceRect.Top + (FSourceRect.Bottom - FSourceRect.Top) div 2 - (FDestRect.Bottom - DestRect.Top) / Zoom / 2),
484 Trunc((DestRect.Right - DestRect.Left) / Zoom),
485 Trunc((DestRect.Bottom - DestRect.Top) / Zoom));
486end;
487
488function TView.PointDestToSrc(Pos: TPoint): TPoint;
489begin
490 Result := Point(Trunc(Pos.X / FZoom + FSourceRect.Left),
491 Trunc(Pos.Y / FZoom + FSourceRect.Top));
492end;
493
494function TView.PointSrcToDest(Pos: TPoint): TPoint;
495begin
496 Result := Point(Trunc((Pos.X - FSourceRect.Left) * FZoom),
497 Trunc((Pos.Y - FSourceRect.Top) * FZoom));
498end;
499
500constructor TView.Create;
501begin
502 Zoom := 1;
503end;
504
505{ TTracks }
506
507function TTracks.SearchPointUp(TrackPoint: TTrackPoint; Skip: TTrackLink): TTrackLink;
508var
509 I: Integer;
510begin
511 I := 0;
512 while (I < Count) and (TTrackLink(Items[I]).Points[1] <> TrackPoint) and (TTrackLink(Items[I]) <> Skip) do Inc(I);
513 if I < Count then Result := TTrackLink(Items[I])
514 else Result := nil;
515end;
516
517function TTracks.SearchPointDown(TrackPoint: TTrackPoint; Skip: TTrackLink): TTrackLink;
518var
519 I: Integer;
520begin
521 I := 0;
522 while (I < Count) and (TTrackLink(Items[I]).Points[0] <> TrackPoint) and (TTrackLink(Items[I]) <> Skip) do Inc(I);
523 if I < Count then Result := TTrackLink(Items[I])
524 else Result := nil;
525end;
526
527{ TTrackPointsAngleGroup }
528
529function TTrackPointsAngleGroup.SearchAngle(Angle: Double): TTrackPointsAngle;
530var
531 I: Integer;
532begin
533 I := 0;
534 while (I < Count) and (TTrackPointsAngle(Items[I]).Angle <> Angle) do Inc(I);
535 if I < Count then Result := TTrackPointsAngle(Items[I])
536 else Result := nil;
537end;
538
539{ TTrackPointsAngle }
540
541constructor TTrackPointsAngle.Create;
542begin
543 TrackLinks := TTrackLinks.Create;
544 TrackLinks.OwnsObjects := False;
545end;
546
547destructor TTrackPointsAngle.Destroy;
548begin
549 TrackLinks.Free;
550 inherited Destroy;
551end;
552
553{ TTrackPoint }
554
555procedure TTrackPoint.Connect(TrackPoint: TTrackPoint);
556var
557 NewLink: TTrackLink;
558begin
559 if NeighPoints.IndexOf(TrackPoint) = -1 then begin
560 NeighPoints.Add(TrackPoint);
561 TrackPoint.NeighPoints.Add(Self);
562
563 // Add new link to both self and connected track point
564 NewLink := Track.Links.AddNew;
565 NewLink.Points.Add(TrackPoint);
566 NewLink.Points.Add(Self);
567 NeighLinks.Add(NewLink);
568 TrackPoint.NeighLinks.Add(NewLink);
569 Track.Links.Add(NewLink);
570 end else raise Exception.Create('Trying to connect already connected track point');
571end;
572
573procedure TTrackPoint.Disconnect(TrackPoint: TTrackPoint);
574var
575 Index: Integer;
576 Link: TTrackLink;
577begin
578 Index := NeighPoints.IndexOf(TrackPoint);
579 if NeighPoints.IndexOf(TrackPoint) <> -1 then begin
580 NeighPoints.Delete(Index);
581 TrackPoint.NeighPoints.Remove(Self);
582
583 // Remove link from both track points
584 Link := NeighLinks.SearchPoints(Self, TrackPoint);
585 NeighLinks.Remove(Link);
586 TrackPoint.NeighLinks.Remove(Link);
587 Track.Links.Remove(Link);
588 end else raise Exception.Create('Trying to disconnect not connected track point');
589end;
590
591function TTrackPoint.GetDown: TTrackPoint;
592var
593 I: Integer;
594begin
595 I := Track.Points.IndexOf(Self) - 1;
596 while (I >= 0) and not Assigned(TTrackPoint(Track.Points[I]).LineStation) do
597 Dec(I);
598 if I >= 0 then Result := TTrackPoint(Track.Points[I])
599 else Result := nil;
600end;
601
602function TTrackPoint.GetUp: TTrackPoint;
603var
604 I: Integer;
605begin
606 I := Track.Points.IndexOf(Self) + 1;
607 while (I < Track.Points.Count) and not Assigned(TTrackPoint(Track.Points[I]).LineStation) do
608 Inc(I);
609 if I < Track.Points.Count then Result := TTrackPoint(Track.Points[I])
610 else Result := nil;
611end;
612
613function TTrackPoint.GetNeighDown: TTrackPoint;
614var
615 NewIndex: Integer;
616begin
617 Result := nil;
618 NewIndex := Track.Points.IndexOf(Self) - 1;
619 if NewIndex >= 0 then Result := TTrackPoint(Track.Points[NewIndex]);
620end;
621
622function TTrackPoint.GetNeighUp: TTrackPoint;
623var
624 NewIndex: Integer;
625begin
626 Result := nil;
627 if Assigned(Track) then begin
628 NewIndex := Track.Points.IndexOf(Self) + 1;
629 if NewIndex < Track.Points.Count then Result := TTrackPoint(Track.Points[NewIndex]);
630 end;
631end;
632
633function TTrackPoint.GetLinkDown: TTrackLink;
634begin
635 if Assigned(LinkDown) then Result := LinkDown
636 else begin
637 LinkDown := TTrackLink.Create;
638 LinkDown.Points.Add(GetNeighDown);
639 LinkDown.Points.Add(Self);
640 Result := LinkDown;
641 GetNeighDown.LinkUp := LinkDown;
642 end;
643end;
644
645function TTrackPoint.GetLinkUp: TTrackLink;
646begin
647 if Assigned(LinkUp) then Result := LinkUp
648 else begin
649 LinkUp := TTrackLink.Create;
650 LinkUp.Points.Add(Self);
651 LinkUp.Points.Add(GetNeighUp);
652 Result := LinkUp;
653 GetNeighUp.LinkDown := LinkUp;
654 end;
655end;
656
657function TTrackPoint.GetDistance: Integer;
658var
659 Index: Integer;
660begin
661 Index := Track.Points.IndexOf(Self);
662 Result := Distance(TTrackPoint(Track.Points[Index + 1]).Position,
663 TTrackPoint(Track.Points[Index]).Position);
664end;
665
666constructor TTrackPoint.Create;
667begin
668 NeighPoints := TTrackPoints.Create;
669 NeighPoints.OwnsObjects := False;
670 NeighLinks := TTrackLinks.Create;
671 NeighLinks.OwnsObjects := False;
672end;
673
674destructor TTrackPoint.Destroy;
675begin
676 NeighLinks.Free;
677 NeighPoints.Free;
678 inherited Destroy;
679end;
680
681{ TLineStations }
682
683function TLineStations.SearchMapStation(Station: TMapStation): TLineStation;
684var
685 I: Integer;
686begin
687 I := 0;
688 while (I < Count) and (TLineStation(Items[I]).MapStation <> Station) do Inc(I);
689 if I < Count then Result := TLineStation(Items[I])
690 else Result := nil;
691end;
692
693{ TMetroPassengers }
694
695function TMetroPassengers.AddNew: TMetroPassenger;
696begin
697 Result := TMetroPassenger.Create;
698 Result.Engine := Engine;
699 Result.Shape := TStationShape(Random(Integer(Engine.ShapeCount)));
700 Add(Result);
701end;
702
703{ TMetroTrains }
704
705function TMetroTrains.GetUnusedTrain: TMetroTrain;
706var
707 I: Integer;
708begin
709 I := 0;
710 while (I < Count) and (Assigned(TMetroTrain(Items[I]).Line)) do Inc(I);
711 if I < Count then Result := TMetroTrain(Items[I])
712 else Result := nil;
713end;
714
715function TMetroTrains.GetUnusedCount: Integer;
716var
717 I: Integer;
718begin
719 Result := 0;
720 for I := 0 to Count - 1 do
721 if not Assigned(TMetroTrain(Items[I]).Line) then Inc(Result);
722end;
723
724function TMetroTrains.AddNew: TMetroTrain;
725begin
726 Result := TMetroTrain.Create;
727 Add(Result);
728end;
729
730{ TMapStations }
731
732function TMapStations.GetRect: TRect;
733var
734 I: Integer;
735begin
736 if Count > 0 then begin
737 with TMapStation(Items[0]) do
738 Result := Rect(Position.X, Position.Y, Position.X, Position.Y);
739 for I := 1 to Count - 1 do
740 with TMapStation(Items[I]) do begin
741 if Position.X < Result.Left then Result.Left := Position.X;
742 if Position.X > Result.Right then Result.Right := Position.X;
743 if Position.Y < Result.Top then Result.Top := Position.Y;
744 if Position.Y > Result.Bottom then Result.Bottom := Position.Y;
745 end;
746 end else Result := Rect(0, 0, 0, 0);
747end;
748
749function TMapStations.AddNew: TMapStation;
750var
751 D: Integer;
752 MinD: Integer;
753 I: Integer;
754 Pass: Integer;
755 Angle: Double;
756 L: Integer;
757const
758 Step = 20;
759begin
760 Result := TMapStation.Create;
761 Result.Engine := Engine;
762 Angle := Random * 2 * Pi;
763 // Ensure minimum distance between stations
764 Pass := 0;
765 L := Step;
766 repeat
767 Result.Position := Point(Trunc(Engine.Map.Size.X / 2 + Cos(Angle) * L * 1.5),
768 Trunc(Engine.Map.Size.Y / 2 + Sin(Angle) * L));
769 MinD := High(Integer);
770 for I := 0 to Engine.Stations.Count - 1 do begin
771 D := Distance(TMapStation(Engine.Stations[I]).Position, Result.Position);
772 if D < MinD then MinD := D;
773 end;
774 Inc(Pass);
775 L := L + StationMinDistance div 2;
776 until (MinD > StationMinDistance) or
777 (Pass > 1000) or (Engine.Stations.Count = 0);
778 Result.Shape := TStationShape(Random(Integer(Engine.ShapeCount)));
779 Add(Result);
780 Engine.ComputeShapeDistance;
781end;
782
783{ TMetroLines }
784
785function TMetroLines.AddNew: TMetroLine;
786begin
787 Result := TMetroLine.Create;
788 Result.Color := LineColors[Count];
789 Result.Engine := Engine;
790 Result.Index := Count;
791 Add(Result);
792end;
793
794function TMetroLines.SearchByColor(Color: TColor): TMetroLine;
795var
796 I: Integer;
797begin
798 I := 0;
799 while (I < Count) and (TMetroLine(Items[I]).Color <> Color) do Inc(I);
800 if I < Count then Result := TMetroLine(Items[I])
801 else Result := nil;
802end;
803
804{ TMetroLine }
805
806procedure TMetroLine.UpdateEndingLines;
807var
808 Index: Integer;
809 NewTrackPoint: TTrackPoint;
810 Angle: Double;
811 EndPoint: TPoint;
812begin
813 if LineStations.Count >= 2 then begin
814 Index := Track.Points.IndexOf(TLineStation(LineStations.First).TrackPoint);
815 if Index = 0 then begin
816 NewTrackPoint := Track.Points.AddNew;
817 Track.Points.Insert(0, NewTrackPoint);
818 end;
819 Index := Track.Points.IndexOf(TLineStation(LineStations.Last).TrackPoint);
820 if Index = Track.Points.Count - 1 then begin
821 NewTrackPoint := Track.Points.AddNew;
822 Track.Points.Insert(Track.Points.Count, NewTrackPoint);
823 end;
824
825 Angle := ArcTan2((TTrackPoint(Track.Points[2]).PositionDesigned.Y - TTrackPoint(Track.Points[1]).PositionDesigned.Y),
826 (TTrackPoint(Track.Points[2]).PositionDesigned.X - TTrackPoint(Track.Points[1]).PositionDesigned.X));
827 EndPoint := Point(Round(TTrackPoint(Track.Points[1]).PositionDesigned.X - EndStationLength * Cos(Angle)),
828 Round(TTrackPoint(Track.Points[1]).PositionDesigned.Y - EndStationLength * Sin(Angle)));
829 TTrackPoint(Track.Points.First).PositionDesigned := EndPoint;
830 TTrackPoint(Track.Points.First).Position := EndPoint;
831
832 Angle := ArcTan2((TTrackPoint(Track.Points[Track.Points.Count - 2]).PositionDesigned.Y - TTrackPoint(Track.Points[Track.Points.Count - 3]).PositionDesigned.Y),
833 (TTrackPoint(Track.Points[Track.Points.Count - 2]).PositionDesigned.X - TTrackPoint(Track.Points[Track.Points.Count - 3]).PositionDesigned.X));
834 EndPoint := Point(Round(TTrackPoint(Track.Points[Track.Points.Count - 2]).PositionDesigned.X + EndStationLength * Cos(Angle)),
835 Round(TTrackPoint(Track.Points[Track.Points.Count - 2]).PositionDesigned.Y + EndStationLength * Sin(Angle)));
836 TTrackPoint(Track.Points.Last).PositionDesigned := EndPoint;
837 TTrackPoint(Track.Points.Last).Position := EndPoint;
838 end;
839end;
840
841procedure TMetroLine.ConnectStation(Station: TMapStation; LineStationDown, LineStationUp: TLineStation);
842var
843 Train: TMetroTrain;
844 NewTrackPoint: TTrackPoint;
845 NewLineStation: TLineStation;
846 Index: Integer;
847begin
848 if not Assigned(Station) then
849 raise Exception.Create('Station have to be defined');
850 if not Assigned(LineStationDown) and not Assigned(LineStationUp) and (LineStations.Count > 0) then
851 raise Exception.Create('No old line station to connect new station');
852 NewLineStation := TLineStation.Create;
853 NewLineStation.Line := Self;
854 NewLineStation.MapStation := Station;
855 Index := 0;
856 if Assigned(LineStationDown) then Index := LineStations.IndexOf(LineStationDown) + 1
857 else if Assigned(LineStationDown) then Index := LineStations.IndexOf(LineStationUp);
858 LineStations.Insert(Index, NewLineStation);
859 Station.Lines.Add(Self);
860
861 NewTrackPoint := Track.Points.AddNew;
862 NewTrackPoint.LineStation := NewLineStation;
863 NewTrackPoint.Position := Station.Position;
864 NewTrackPoint.PositionDesigned := NewTrackPoint.Position;
865 Index := 0;
866 if Assigned(LineStationDown) then Index := Track.Points.IndexOf(LineStationDown.TrackPoint) + 1
867 else if Assigned(LineStationUp) then Index := Track.Points.IndexOf(LineStationUp.TrackPoint);
868 Track.Points.Insert(Index, NewTrackPoint);
869 NewLineStation.TrackPoint := NewTrackPoint;
870
871 if Assigned(LineStationDown) then
872 Track.RouteTrack(NewLineStation.TrackPoint.GetDown, NewLineStation.TrackPoint);
873 if Assigned(LineStationUp) then
874 Track.RouteTrack(NewLineStation.TrackPoint, NewLineStation.TrackPoint.GetUp);
875
876 // Place one train if at least two stations present
877 if (LineStations.Count = 2) then begin
878 Train := Engine.Trains.GetUnusedTrain;
879 if Assigned(Train) then begin
880 Train.Line := Self;
881 Train.TargetStation := TLineStation(LineStations[0]);
882 Train.BaseTrackPoint := TTrackPoint(Track.Points.First);
883 Trains.Add(Train);
884 end;
885 end;
886 UpdateEndingLines;
887 Engine.ComputeShapeDistance;
888 Engine.ShiftTrackPoints;
889end;
890
891procedure TMetroLine.DisconnectStation(ALineStation: TLineStation);
892var
893 I: Integer;
894 J: Integer;
895 Index: Integer;
896 TP1, TP2: TTrackPoint;
897 IsOnTrack: Boolean;
898begin
899 // Determine track point range to be removed
900 TP1 := ALineStation.TrackPoint.GetDown;
901 if not Assigned(TP1) then TP1 := TTrackPoint(Track.Points.First);
902 TP2 := ALineStation.TrackPoint.GetUp;
903 if not Assigned(TP2) then TP2 := TTrackPoint(Track.Points.Last);
904
905 // Remove track points from trains
906 for I := 0 to Trains.Count - 1 do
907 with TMetroTrain(Trains[I]) do begin
908 IsOnTrack := False;
909 for J := Track.Points.IndexOf(TP1) to Track.Points.IndexOf(TP2) do
910 if TTrackPoint(Track.Points[J]) = BaseTrackPoint then begin
911 IsOnTrack := True;
912 Break;
913 end;
914 if IsOnTrack then begin
915 if Assigned(BaseTrackPoint) and Assigned(BaseTrackPoint.GetUp) and (BaseTrackPoint.GetUp <> ALineStation.TrackPoint) then
916 BaseTrackPoint := BaseTrackPoint.GetUp
917 else
918 if Assigned(BaseTrackPoint) and Assigned(BaseTrackPoint.GetDown) and (BaseTrackPoint.GetDown <> ALineStation.TrackPoint) then
919 BaseTrackPoint := BaseTrackPoint.GetDown
920 else BaseTrackPoint := nil;
921 end;
922 end;
923
924 // Delete old trackpoints
925 Index := Track.Points.IndexOf(ALineStation.TrackPoint) - 1;
926 while (Index >= 0) and (not Assigned(TTrackPoint(Track.Points[Index]).LineStation)) do begin
927 Track.Points.Delete(Index);
928 Dec(Index);
929 end;
930 Index := Index + 1;
931 Track.Points.Delete(Index);
932 while (Index < Track.Points.Count) and (not Assigned(TTrackPoint(Track.Points[Index]).LineStation)) do
933 Track.Points.Delete(Index);
934
935 if ((Index - 1) >= 0) and (Index < Track.Points.Count) then
936 Track.RouteTrack(TTrackPoint(Track.Points[Index - 1]), TTrackPoint(Track.Points[Index]));
937
938 ALineStation.MapStation.Lines.Remove(Self);
939 Index := LineStations.IndexOf(ALineStation);
940
941 for I := 0 to Trains.Count - 1 do
942 with TMetroTrain(Trains[I]) do begin
943 if TargetStation = ALineStation then
944 TargetStation := TLineStation(LineStations[(Index + 1) mod LineStations.Count]);
945 end;
946
947 LineStations.Delete(Index);
948
949 // Remove all trains if less then two stations
950 if LineStations.Count < 2 then
951 for I := Trains.Count - 1 downto 0 do begin
952 TMetroTrain(Trains[I]).Line := nil;
953 Trains.Delete(I);
954 end;
955 UpdateEndingLines;
956 Engine.ComputeShapeDistance;
957 Engine.ShiftTrackPoints;
958end;
959
960procedure TTrack.RouteTrack(TP1, TP2: TTrackPoint);
961var
962 NewTrackPoint: TTrackPoint;
963 Delta: TPoint;
964 P1, P2: TPoint;
965 Index1, Index2: Integer;
966begin
967 RemoveTrackBetween(TP1, TP2);
968 Index1 := Points.IndexOf(TP1);
969 Index2 := Points.IndexOf(TP2);
970 P1 := TTrackPoint(Points[Index1]).PositionDesigned;
971 P2 := TTrackPoint(Points[Index2]).PositionDesigned;
972 NewTrackPoint := Points.AddNew;
973 Delta := Point(P2.X - P1.X, P2.Y - P1.Y);
974 if Abs(Delta.X) > Abs(Delta.Y) then begin
975 NewTrackPoint.PositionDesigned := Point(P2.X - Sign(Delta.X) * Abs(Delta.Y), P1.Y);
976 NewTrackPoint.Position := NewTrackPoint.PositionDesigned;
977 end else begin
978 NewTrackPoint.PositionDesigned := Point(P1.X, P2.Y - Sign(Delta.Y) * Abs(Delta.X));
979 NewTrackPoint.Position := NewTrackPoint.PositionDesigned;
980 end;
981 Points.Insert(Index1 + 1, NewTrackPoint);
982end;
983
984procedure TTrack.RemoveTrackBetween(TP1, TP2: TTrackPoint);
985var
986 Index1, Index2: Integer;
987 Temp: Integer;
988 I: Integer;
989begin
990 Index1 := Points.IndexOf(TP1);
991 Index2 := Points.IndexOf(TP2);
992 if (Index1 = -1) then
993 raise Exception.Create('TrackPoint1 not found');
994 if (Index2 = -1) then
995 raise Exception.Create('TrackPoint2 not found');
996 if Index1 > Index2 then begin
997 Temp := Index1;
998 Index1 := Index2;
999 Index2 := Temp;
1000 end;
1001 for I := 1 to Index2 - Index1 - 1 do
1002 Points.Delete(Index1 + 1);
1003end;
1004
1005function TMetroLine.GetTrackLength: Integer;
1006var
1007 I: Integer;
1008begin
1009 Result := 0;
1010 for I := 0 to Track.Points.Count - 1 do
1011 if I > 0 then
1012 Result := Result + Distance(TTrackPoint(Track.Points[I]).Position, TTrackPoint(Track.Points[I - 1]).Position);
1013end;
1014
1015constructor TMetroLine.Create;
1016begin
1017 LineStations := TLineStations.Create;
1018 LineStations.OwnsObjects := True;
1019 Trains := TMetroTrains.Create;
1020 Trains.OwnsObjects := False;
1021 Track := TTrack.Create;
1022 Track.Line := Self;
1023end;
1024
1025destructor TMetroLine.Destroy;
1026begin
1027 Trains.Free;
1028 LineStations.Free;
1029 Track.Free;
1030 inherited Destroy;
1031end;
1032
1033function TMetroLine.IsCircular: Boolean;
1034begin
1035 Result := False;
1036 if LineStations.Count >= 2 then
1037 Result := (TLineStation(LineStations.Last).MapStation = TLineStation(LineStations.First).MapStation);
1038end;
1039
1040{ TMetroTrain }
1041
1042procedure TMetroTrain.SetLine(AValue: TMetroLine);
1043begin
1044 if FLine = AValue then Exit;
1045 FLine := AValue;
1046 if AValue = nil then begin
1047 RelPos := 0;
1048 BaseTrackPoint := nil;
1049 TargetStation := nil;
1050 end;
1051end;
1052
1053function TMetroTrain.GetTargetStationDistance: Integer;
1054var
1055 Current: Integer;
1056 Target: Integer;
1057 I: Integer;
1058begin
1059 Result := 0;
1060 if Assigned(BaseTrackPoint) and Assigned(TargetStation) then begin
1061 Current := Line.Track.Points.IndexOf(BaseTrackPoint);
1062 Target := Line.Track.Points.IndexOf(TargetStation.TrackPoint);
1063 if Current < Target then begin
1064 for I := Current to Target - 1 do
1065 Result := Result + TTrackPoint(Line.Track.Points[I]).GetDistance;
1066 Result := Result - Trunc(RelPos);
1067 end else
1068 if Current > Target then begin
1069 for I := Current - 1 downto Target do
1070 Result := Result + TTrackPoint(Line.Track.Points[I]).GetDistance;
1071 Result := Result + Trunc(RelPos);
1072 end else Result := Trunc(RelPos);
1073 end;
1074end;
1075
1076function TMetroTrain.GetPosition: TPoint;
1077var
1078 D: Integer;
1079 Delta: TPoint;
1080 UpPoint: TTrackPoint;
1081begin
1082 Result := Point(0, 0);
1083 if Assigned(BaseTrackPoint) then
1084 with BaseTrackPoint do begin
1085 UpPoint := BaseTrackPoint.GetNeighUp;
1086 if Assigned(UpPoint) then begin
1087 D := Distance(UpPoint.Position, Position);
1088 if D > 0 then begin
1089 Delta := SubPoint(UpPoint.Position, Position);
1090 Result := Point(Trunc(Position.X + Delta.X * RelPos / D),
1091 Trunc(Position.Y + Delta.Y * RelPos / D));
1092 end;
1093 end;
1094 end;
1095end;
1096
1097function TMetroTrain.GetAngle: Double;
1098var
1099 UpPoint: TTrackPoint;
1100begin
1101 Result := 0;
1102 if Assigned(BaseTrackPoint) then
1103 with BaseTrackPoint do begin
1104 UpPoint := BaseTrackPoint.GetNeighUp;
1105 if Assigned(UpPoint) then begin
1106 Result := ArcTan2(UpPoint.Position.Y - Position.Y,
1107 UpPoint.Position.X - Position.X);
1108 end;
1109 end;
1110end;
1111
1112constructor TMetroTrain.Create;
1113begin
1114 Passengers := TMetroPassengers.Create;
1115 Passengers.OwnsObjects := False;
1116 Direction := 1;
1117 Line := nil;
1118end;
1119
1120destructor TMetroTrain.Destroy;
1121begin
1122 Passengers.Free;
1123 inherited Destroy;
1124end;
1125
1126{ TMapStation }
1127
1128procedure TMapStation.ShiftTrackPoints;
1129var
1130 TrackLinks: TTrackLinks;
1131 I: Integer;
1132 J: Integer;
1133 Index: Integer;
1134 TP: TTrackPoint;
1135 LS: TLineStation;
1136 Line: TMetroLine;
1137 Angle: Float;
1138 TPAngleGroup: TTrackPointsAngleGroup;
1139 GroupItem: TTrackPointsAngle;
1140 NewTrackLink: TTrackLink;
1141 HAngle: Double;
1142 P1, P2: TPoint;
1143 NewShift: TPoint;
1144begin
1145 TrackLinks := TTrackLinks.Create;
1146 TrackLinks.OwnsObjects := False;
1147
1148 // Collect all near track points as track links
1149 SortLines;
1150 for I := 0 to Lines.Count - 1 do begin
1151 Line := TMetroLine(Lines[I]);
1152 LS := Line.LineStations.SearchMapStation(Self);
1153 TP := LS.TrackPoint;
1154 Index := Line.Track.Points.IndexOf(TP);
1155 if Index > 0 then begin
1156 NewTrackLink := TTrackPoint(Line.Track.Points[Index]).GetLinkDown;
1157 TrackLinks.Add(NewTrackLink);
1158 end;
1159 if Index < (Line.Track.Points.Count - 1) then begin
1160 NewTrackLink := TTrackPoint(Line.Track.Points[Index]).GetLinkUp;
1161 TrackLinks.Add(NewTrackLink);
1162 end;
1163 if Line.IsCircular and (Self = TLineStation(Line.LineStations.First).MapStation) and
1164 (Self = TLineStation(Line.LineStations.Last).MapStation) then begin
1165 LS := TLineStation(Line.LineStations.Last);
1166 TP := LS.TrackPoint;
1167 Index := Line.Track.Points.IndexOf(TP);
1168 if Index > 0 then begin
1169 NewTrackLink := TTrackPoint(Line.Track.Points[Index]).GetLinkDown;
1170 TrackLinks.Add(NewTrackLink);
1171 end;
1172 if Index < (Line.Track.Points.Count - 1) then begin
1173 NewTrackLink := TTrackPoint(Line.Track.Points[Index]).GetLinkUp;
1174 TrackLinks.Add(NewTrackLink);
1175 end;
1176 end;
1177 end;
1178
1179 // Make groups of TrackLinks with same angle
1180 TPAngleGroup := TTrackPointsAngleGroup.Create;
1181 for I := 0 to TrackLinks.Count - 1 do begin
1182 P1 := TTrackPoint(TTrackLink(TrackLinks[I]).Points[0]).PositionDesigned;
1183 P2 := TTrackPoint(TTrackLink(TrackLinks[I]).Points[1]).PositionDesigned;
1184 if ComparePoint(P1, Position) and not ComparePoint(P2, Position) then begin
1185 Angle := ArcTan2(P2.Y - Position.Y, P2.X - Position.X);
1186 end else
1187 if ComparePoint(P2, Position) and not ComparePoint(P1, Position) then begin
1188 Angle := ArcTan2(P1.Y - Position.Y, P1.X - Position.X);
1189 end else Angle := 0;// else raise Exception.Create('TrackLink angle rrror');
1190
1191 GroupItem := TPAngleGroup.SearchAngle(Angle);
1192 if not Assigned(GroupItem) then begin
1193 GroupItem := TTrackPointsAngle.Create;
1194 GroupItem.Angle := Angle;
1195 TPAngleGroup.Add(GroupItem);
1196 end;
1197 GroupItem.TrackLinks.Add(TTrackLink(TrackLinks[I]))
1198 end;
1199
1200 // Shift TrackLinks according number of lines in group
1201 for I := 0 to TPAngleGroup.Count - 1 do
1202 with TTrackPointsAngle(TPAngleGroup[I]) do begin
1203 for J := 0 to TrackLinks.Count - 1 do
1204 with TTrackLink(TrackLinks[J]) do begin
1205 // Get orthogonal angle
1206 HAngle := Angle + Pi / 2;
1207 if HAngle > Pi then HAngle := HAngle - Pi;
1208 NewShift.X := Trunc(MetroLineThickness * Cos(HAngle) * (J - (TrackLinks.Count - 1) / 2));
1209 NewShift.Y := Trunc(MetroLineThickness * Sin(HAngle) * (J - (TrackLinks.Count - 1) / 2));
1210 //NewShift.X := TrackLinks.Count;
1211 Shift := NewShift;
1212 end;
1213 end;
1214
1215 TPAngleGroup.Free;
1216 TrackLinks.Free;
1217end;
1218
1219function MapStationCompareLine(Item1, Item2: Pointer): Integer;
1220begin
1221 if TMetroLine(Item1).Index > TMetroLine(Item2).Index then Result := 1
1222 else if TMetroLine(Item1).Index < TMetroLine(Item2).Index then Result := -1
1223 else Result := 0;
1224end;
1225
1226procedure TMapStation.SortLines;
1227begin
1228 Lines.Sort(MapStationCompareLine);
1229end;
1230
1231function TMapStation.IsBestStationForShape(Shape: TStationShape;
1232 Check, Current: TLineStation): Boolean;
1233var
1234 I: Integer;
1235 T: Integer;
1236 Distance: Integer;
1237 StationIndex: Integer;
1238 DirectionUp: Boolean;
1239 DirectionDown: Boolean;
1240 NextStationUp: TLineStation;
1241 NextStationDown: TLineStation;
1242 CurrentLineStation: TLineStation;
1243begin
1244 Distance := High(Integer);
1245 for I := 0 to Lines.Count - 1 do
1246 with TMetroLine(Lines[I]) do begin
1247 CurrentLineStation := LineStations.SearchMapStation(Current.MapStation);
1248 StationIndex := LineStations.IndexOf(CurrentLineStation);
1249 if IsCircular then begin
1250 DirectionUp := False;
1251 DirectionDown := False;
1252 for T := 0 to Trains.Count - 1 do begin
1253 if TMetroTrain(Trains[T]).Direction = 1 then DirectionUp := True;
1254 if TMetroTrain(Trains[T]).Direction = -1 then DirectionDown := True;
1255 end;
1256 if StationIndex = 0 then
1257 NextStationDown := TLineStation(LineStations[LineStations.Count - 2])
1258 else
1259 if StationIndex > 0 then
1260 NextStationDown := TLineStation(LineStations[StationIndex - 1]);
1261
1262 if (StationIndex >= 0) and (StationIndex = LineStations.Count - 1) then
1263 NextStationUp := TLineStation(LineStations[1])
1264 else
1265 if (StationIndex >= 0) and (StationIndex < LineStations.Count - 1) then
1266 NextStationUp := TLineStation(LineStations[StationIndex + 1]);
1267 end else begin
1268 if StationIndex > 0 then begin
1269 DirectionDown := True;
1270 NextStationDown := TLineStation(LineStations[StationIndex - 1])
1271 end else DirectionDown := False;
1272 if (StationIndex >= 0) and (StationIndex < LineStations.Count - 1) then begin
1273 DirectionUp := True;
1274 NextStationUp := TLineStation(LineStations[StationIndex + 1]);
1275 end else DirectionUp := False;
1276 end;
1277 if DirectionDown and (NextStationDown.MapStation.ShapeDistance[Shape] <> -1) and
1278 (NextStationDown.MapStation.ShapeDistance[Shape] < Distance) then begin
1279 Distance := NextStationDown.MapStation.ShapeDistance[Shape];
1280 end;
1281 if DirectionUp and (NextStationUp.MapStation.ShapeDistance[Shape] <> -1) and
1282 (NextStationUp.MapStation.ShapeDistance[Shape] < Distance) then begin
1283 Distance := NextStationUp.MapStation.ShapeDistance[Shape];
1284 end;
1285 end;
1286 Result := (Check.MapStation.ShapeDistance[Shape] <> -1) and (Check.MapStation.ShapeDistance[Shape] <= Distance);
1287end;
1288
1289constructor TMapStation.Create;
1290begin
1291 Passengers := TMetroPassengers.Create;
1292 Passengers.OwnsObjects := False;
1293 Lines := TMetroLines.Create;
1294 Lines.OwnsObjects := False;
1295end;
1296
1297destructor TMapStation.Destroy;
1298begin
1299 Lines.Free;
1300 Passengers.Free;
1301 inherited Destroy;
1302end;
1303
1304{ TEngine }
1305
1306procedure TEngine.ResizeView;
1307var
1308 NewPoint: TPoint;
1309begin
1310 // Need to see all stations on screen
1311 View.SourceRect := RectEnlarge(Stations.GetRect, 100);
1312
1313 NewPoint := Point(
1314 Trunc((View.SourceRect.Left + (View.SourceRect.Right - View.SourceRect.Left) / 2) -
1315 (View.DestRect.Left + (View.DestRect.Right - View.DestRect.Left) / 2 / View.Zoom)),
1316 Trunc((View.SourceRect.Top + (View.SourceRect.Bottom - View.SourceRect.Top) / 2) -
1317 (View.DestRect.Top + (View.DestRect.Bottom - View.DestRect.Top) / 2 / View.Zoom)));
1318 View.SourceRect := Bounds(NewPoint.X, NewPoint.Y, Trunc((View.DestRect.Right - View.DestRect.Left) / View.Zoom),
1319 Trunc((View.DestRect.Bottom - View.DestRect.Top) / View.Zoom));
1320end;
1321
1322function TEngine.GetExistStationShapes: TStationShapeSet;
1323var
1324 I: Integer;
1325begin
1326 Result := [];
1327 for I := 0 to Stations.Count - 1 do
1328 with TMapStation(Stations[I]) do begin
1329 Result := Result + [Shape];
1330 end;
1331end;
1332
1333function TEngine.GetStationOnPos(Pos: TPoint): TMapStation;
1334var
1335 I: Integer;
1336const
1337 ClickDistance = 30;
1338begin
1339 I := 0;
1340 while (I < Stations.Count) and (Distance(TMapStation(Stations[I]).Position, Pos) > ClickDistance) do Inc(I);
1341 if I < Stations.Count then Result := TMapStation(Stations[I])
1342 else Result := nil;
1343end;
1344
1345function TEngine.GetTrackOnPos(Pos: TPoint): TTrackLink;
1346var
1347 I: Integer;
1348 T: Integer;
1349 D: Integer;
1350 MinD: Integer;
1351begin
1352 Result := TTrackLink.Create;
1353 Result.Points.Count := 2;
1354 Result.Points[0] := nil;
1355 Result.Points[1] := nil;
1356 I := 0;
1357 MinD := High(Integer);
1358 while (I < Lines.Count) do
1359 with TMetroLine(Lines[I]) do begin
1360 for T := 1 to Track.Points.Count - 1 do begin
1361 D := PointToLineDistance(Pos, TTrackPoint(Track.Points[T - 1]).Position, TTrackPoint(Track.Points[T]).Position);
1362 if (D < MinD) and (D < TrackClickDistance) then begin
1363 MinD := D;
1364 Result.Points[0] := TTrackPoint(Track.Points[T - 1]);
1365 Result.Points[1] := TTrackPoint(Track.Points[T]);
1366 end;
1367 end;
1368 Inc(I);
1369 end;
1370end;
1371
1372function TEngine.GetTrainOnPos(Pos: TPoint): TMetroTrain;
1373var
1374 I: Integer;
1375 MinDistance: Integer;
1376 D: Integer;
1377begin
1378 Result := nil;
1379 MinDistance := High(Integer);
1380 for I := 0 to Trains.Count - 1 do
1381 with TMetroTrain(Trains[I]) do begin
1382 D := Distance(GetPosition, Pos);
1383 if (D < (TrainSize div 2)) and (D < MinDistance) then begin
1384 Result := TMetroTrain(Trains[I]);
1385 MinDistance := D;
1386 end;
1387 end;
1388end;
1389
1390procedure TEngine.DrawLine(Canvas: TCanvas; Pos: TPoint);
1391var
1392 Delta: TPoint;
1393begin
1394 Delta := Point(Pos.X - Canvas.PenPos.X, Pos.Y - Canvas.PenPos.Y);
1395 if Abs(Delta.X) > Abs(Delta.Y) then begin
1396 Canvas.LineTo(Pos.X - Sign(Delta.X) * Abs(Delta.Y), Canvas.PenPos.Y);
1397 end else begin
1398 Canvas.LineTo(Canvas.PenPos.X, Pos.Y - Sign(Delta.Y) * Abs(Delta.X));
1399 end;
1400 Canvas.LineTo(Pos.X, Pos.Y);
1401end;
1402
1403procedure TEngine.DrawShape(Canvas: TCanvas; Position: TPoint; Shape: TStationShape;
1404 Size: Integer; Angle: Double);
1405var
1406 Points: array of TPoint;
1407 I: Integer;
1408 Angle2: Double;
1409begin
1410 case Shape of
1411 ssSquare: begin
1412 SetLength(Points, 4);
1413 Points[0] := Point(Position.X - Size div 2, Position.Y - Size div 2);
1414 Points[1] := Point(Position.X + Size div 2, Position.Y - Size div 2);
1415 Points[2] := Point(Position.X + Size div 2, Position.Y + Size div 2);
1416 Points[3] := Point(Position.X - Size div 2, Position.Y + Size div 2);
1417 Points := RotatePoints(Position, Points, Angle);
1418 Canvas.Polygon(Points);
1419 end;
1420 ssCircle: Canvas.Ellipse(
1421 Position.X - Size div 2, Position.Y - Size div 2,
1422 Position.X + Size div 2, Position.Y + Size div 2);
1423 ssTriangle: begin
1424 SetLength(Points, 3);
1425 Points[0] := Point(Position.X, Position.Y - Size div 2);
1426 Points[1] := Point(Position.X + Size div 2, Position.Y + Size div 2);
1427 Points[2] := Point(Position.X - Size div 2, Position.Y + Size div 2);
1428 Points := RotatePoints(Position, Points, Angle);
1429 Canvas.Polygon(Points);
1430 end;
1431 ssStar: begin
1432 SetLength(Points, 10);
1433 for I := 0 to 9 do begin
1434 Angle2 := I / 10 * 2 * Pi - Pi / 2;
1435 if (I mod 2) = 0 then
1436 Points[I] := Point(Round(Position.X + Cos(Angle2) * Size / 2),
1437 Round(Position.Y + Sin(Angle2) * Size / 2))
1438 else
1439 Points[I] := Point(Round(Position.X + Cos(Angle2) * Size / 5),
1440 Round(Position.Y + Sin(Angle2) * Size / 5));
1441 end;
1442 Points := RotatePoints(Position, Points, Angle);
1443 Canvas.Polygon(Points);
1444 end;
1445 ssPlus: begin
1446 SetLength(Points, 12);
1447 Points[0] := Point(Position.X + Size div 6, Position.Y - Size div 6);
1448 Points[1] := Point(Position.X + Size div 2, Position.Y - Size div 6);
1449 Points[2] := Point(Position.X + Size div 2, Position.Y + Size div 6);
1450 Points[3] := Point(Position.X + Size div 6, Position.Y + Size div 6);
1451 Points[4] := Point(Position.X + Size div 6, Position.Y + Size div 2);
1452 Points[5] := Point(Position.X - Size div 6, Position.Y + Size div 2);
1453 Points[6] := Point(Position.X - Size div 6, Position.Y + Size div 6);
1454 Points[7] := Point(Position.X - Size div 2, Position.Y + Size div 6);
1455 Points[8] := Point(Position.X - Size div 2, Position.Y - Size div 6);
1456 Points[9] := Point(Position.X - Size div 6, Position.Y - Size div 6);
1457 Points[10] := Point(Position.X - Size div 6, Position.Y - Size div 2);
1458 Points[11] := Point(Position.X + Size div 6, Position.Y - Size div 2);
1459 Points := RotatePoints(Position, Points, Angle);
1460 Canvas.Polygon(Points);
1461 end;
1462 ssPentagon: begin
1463 SetLength(Points, 5);
1464 for I := 0 to 4 do begin
1465 Angle2 := I / 5 * 2 * Pi - Pi / 2;
1466 Points[I] := Point(Round(Position.X + Cos(Angle2) * Size / 2),
1467 Round(Position.Y + Sin(Angle2) * Size / 2));
1468 end;
1469 Points := RotatePoints(Position, Points, Angle);
1470 Canvas.Polygon(Points);
1471 end;
1472 ssHexagon: begin
1473 SetLength(Points, 6);
1474 for I := 0 to 5 do begin
1475 Angle2 := I / 6 * 2 * Pi - Pi / 2;
1476 Points[I] := Point(Round(Position.X + Cos(Angle2) * Size / 2),
1477 Round(Position.Y + Sin(Angle2) * Size / 2));
1478 end;
1479 Points := RotatePoints(Position, Points, Angle);
1480 Canvas.Polygon(Points);
1481 end;
1482 ssDiamond: begin
1483 SetLength(Points, 4);
1484 Points[0] := Point(Position.X, Position.Y - Size div 2);
1485 Points[1] := Point(Position.X + Size div 2, Position.Y);
1486 Points[2] := Point(Position.X, Position.Y + Size div 2);
1487 Points[3] := Point(Position.X - Size div 2, Position.Y);
1488 Points := RotatePoints(Position, Points, Angle);
1489 Canvas.Polygon(Points);
1490 end;
1491 ssCross: begin
1492 SetLength(Points, 12);
1493 Points[0] := Point(Position.X + Size div 6, Position.Y - Size div 6);
1494 Points[1] := Point(Position.X + Size div 2, Position.Y - Size div 6);
1495 Points[2] := Point(Position.X + Size div 2, Position.Y + Size div 6);
1496 Points[3] := Point(Position.X + Size div 6, Position.Y + Size div 6);
1497 Points[4] := Point(Position.X + Size div 6, Position.Y + Size div 2);
1498 Points[5] := Point(Position.X - Size div 6, Position.Y + Size div 2);
1499 Points[6] := Point(Position.X - Size div 6, Position.Y + Size div 6);
1500 Points[7] := Point(Position.X - Size div 2, Position.Y + Size div 6);
1501 Points[8] := Point(Position.X - Size div 2, Position.Y - Size div 6);
1502 Points[9] := Point(Position.X - Size div 6, Position.Y - Size div 6);
1503 Points[10] := Point(Position.X - Size div 6, Position.Y - Size div 2);
1504 Points[11] := Point(Position.X + Size div 6, Position.Y - Size div 2);
1505 Points := RotatePoints(Position, Points, Angle + Pi / 4);
1506 Canvas.Polygon(Points);
1507 end;
1508 ssHalfCircle: Canvas.Pie(
1509 Position.X - Size div 2, Position.Y - Size div 2,
1510 Position.X + Size div 2, Position.Y + Size div 2,
1511 Position.X - Size div 2, Position.Y,
1512 Position.X + Size div 2, Position.Y);
1513 ssQuarterCircle: Canvas.Pie(
1514 Position.X - Size div 2 - Size, Position.Y - Size div 2,
1515 Position.X + Size div 2, Position.Y + Size div 2 + Size,
1516 Position.X + Size div 2, Position.Y + Size div 2,
1517 Position.X - Size div 2, Position.Y - Size div 2);
1518 ssHeptagon: begin
1519 SetLength(Points, 8);
1520 for I := 0 to High(Points) do begin
1521 Angle2 := I / Length(Points) * 2 * Pi - Pi / 2;
1522 Points[I] := Point(Round(Position.X + Cos(Angle2) * Size / 2),
1523 Round(Position.Y + Sin(Angle2) * Size / 2));
1524 end;
1525 Points := RotatePoints(Position, Points, Angle);
1526 Canvas.Polygon(Points);
1527 end;
1528 end;
1529end;
1530
1531procedure TEngine.ComputeShapeDistance;
1532var
1533 I: Integer;
1534 S: TStationShape;
1535begin
1536 // Reset all distances
1537 for I := 0 to Stations.Count - 1 do
1538 with TMapStation(Stations[I]) do begin
1539 for S := Low(ShapeDistance) to High(ShapeDistance) do
1540 ShapeDistance[S] := -1;
1541 end;
1542
1543 // Propagate shape distance for all stations
1544 // Distace 0 means that station is final target
1545 for I := 0 to Stations.Count - 1 do
1546 with TMapStation(Stations[I]) do begin
1547 ComputeShapeDistanceStation(TMapStation(Stations[I]), Shape, 0);
1548 end;
1549end;
1550
1551procedure TEngine.ComputeShapeDistanceStation(Station: TMapStation;
1552 UpdatedShape: TStationShape; Distance: Integer);
1553var
1554 I: Integer;
1555 T: Integer;
1556 StationIndex: Integer;
1557 DirectionDown: Boolean;
1558 DirectionUp: Boolean;
1559begin
1560 with Station do begin
1561 if (Distance < ShapeDistance[UpdatedShape]) or (ShapeDistance[UpdatedShape] = -1) then begin
1562 ShapeDistance[UpdatedShape] := Distance;
1563 // Do for all lines connected to station
1564 for I := 0 to Lines.Count - 1 do
1565 with TMetroLine(Lines[I]) do
1566 for StationIndex := 0 to LineStations.Count - 1 do
1567 if TLineStation(LineStations[StationIndex]).MapStation = Station then begin
1568 if not IsCircular then begin
1569 // Update for all adjecent stations
1570 if StationIndex > 0 then
1571 ComputeShapeDistanceStation(TLineStation(LineStations[StationIndex - 1]).MapStation,
1572 UpdatedShape, Station.ShapeDistance[UpdatedShape] + 1);
1573 if (StationIndex >= 0) and (StationIndex < LineStations.Count - 1) then
1574 ComputeShapeDistanceStation(TLineStation(LineStations[StationIndex + 1]).MapStation,
1575 UpdatedShape, Station.ShapeDistance[UpdatedShape] + 1);
1576 end else begin
1577 // If circular then trains might go in single direction so passengers
1578 // waiting for oposite directions are wrong
1579 DirectionUp := False;
1580 DirectionDown := False;
1581 for T := 0 to Trains.Count - 1 do begin
1582 if TMetroTrain(Trains[T]).Direction = 1 then DirectionUp := True;
1583 if TMetroTrain(Trains[T]).Direction = -1 then DirectionDown := True;
1584 end;
1585 // Update for all adjecent stations
1586 if DirectionUp then begin
1587 if StationIndex = 0 then
1588 ComputeShapeDistanceStation(TLineStation(LineStations[LineStations.Count - 2]).MapStation,
1589 UpdatedShape, Station.ShapeDistance[UpdatedShape] + 1);
1590 if StationIndex > 0 then
1591 ComputeShapeDistanceStation(TLineStation(LineStations[StationIndex - 1]).MapStation,
1592 UpdatedShape, Station.ShapeDistance[UpdatedShape] + 1);
1593 end;
1594 if DirectionDown then begin
1595 if (StationIndex >= 0) and (StationIndex = LineStations.Count - 1) then
1596 ComputeShapeDistanceStation(TLineStation(LineStations[1]).MapStation,
1597 UpdatedShape, Station.ShapeDistance[UpdatedShape] + 1);
1598 if (StationIndex >= 0) and (StationIndex < LineStations.Count - 1) then
1599 ComputeShapeDistanceStation(TLineStation(LineStations[StationIndex + 1]).MapStation,
1600 UpdatedShape, Station.ShapeDistance[UpdatedShape] + 1);
1601 end;
1602 end;
1603 end;
1604 end;
1605 end;
1606end;
1607
1608procedure TEngine.TrainMovement;
1609var
1610 I: Integer;
1611 CurrentStation: TLineStation;
1612 P: Integer;
1613 Passenger: TMetroPassenger;
1614 PosDelta: Integer;
1615 TargetStationIndex: Integer;
1616 PosChange: Double;
1617 TP: TTrackPoint;
1618begin
1619 // Move trains
1620 for I := 0 to Trains.Count - 1 do
1621 with TMetroTrain(Trains[I]) do begin
1622 if not Assigned(TargetStation) and Assigned(BaseTrackPoint) then begin
1623 if (Direction <> 1) and (Direction <> -1) then Direction := 1
1624 else Direction := -Direction;
1625 TP := BaseTrackPoint.GetUp;
1626 if Assigned(TP) then TargetStation := TP.LineStation
1627 else begin
1628 TP := BaseTrackPoint.GetDown;
1629 if Assigned(TP) then TargetStation := TP.LineStation;
1630 end;
1631 end;
1632 if Assigned(Line) then begin
1633 if InStation then begin
1634 if (Time - StationStopTime) > OneHour then begin
1635 CurrentStation := TargetStation;
1636
1637 // Choose next target station
1638 TargetStationIndex := Line.LineStations.IndexOf(TargetStation) + Direction;
1639 if TargetStationIndex < 0 then begin
1640 if Line.IsCircular then begin
1641 TargetStationIndex := Line.LineStations.Count - 2;
1642 BaseTrackPoint := TLineStation(Line.LineStations.Last).TrackPoint;
1643 RelPos := 0;
1644 end else begin
1645 TargetStationIndex := 1;
1646 Direction := -Direction;
1647 end;
1648 end else
1649 if TargetStationIndex >= Line.LineStations.Count then begin
1650 if Line.IsCircular then begin
1651 TargetStationIndex := 1;
1652 BaseTrackPoint := TLineStation(Line.LineStations.First).TrackPoint;
1653 RelPos := 0;
1654 end else begin
1655 TargetStationIndex := Line.LineStations.Count - 2;
1656 Direction := -Direction;
1657 end;
1658 end;
1659 TargetStation := TLineStation(Line.LineStations[TargetStationIndex]);
1660
1661 // Unload passengers in target station
1662 if Assigned(CurrentStation) then
1663 for P := Passengers.Count - 1 downto 0 do begin
1664 if TMetroPassenger(Passengers[P]).Shape = CurrentStation.MapStation.Shape then begin
1665 Passenger := TMetroPassenger(Passengers[P]);
1666 Passengers.Delete(P);
1667 Self.Passengers.Remove(Passenger);
1668 Inc(ServedPassengerCount);
1669 end;
1670 end;
1671 // Unload passengers to change line
1672 if Assigned(CurrentStation) then
1673 for P := Passengers.Count - 1 downto 0 do begin
1674 if not CurrentStation.MapStation.IsBestStationForShape(TMetroPassenger(Passengers[P]).Shape,
1675 TargetStation, CurrentStation) then begin
1676 Passenger := TMetroPassenger(Passengers[P]);
1677 Passengers.Delete(P);
1678 CurrentStation.MapStation.Passengers.Add(Passenger);
1679 Passenger.Station := CurrentStation.MapStation;
1680 end;
1681 end;
1682
1683 // Load new passengers
1684 if Assigned(CurrentStation) and not Assigned(CurrentStation.MapStation) then
1685 raise Exception.Create('Station have to have MapStation');
1686 if Assigned(CurrentStation) then
1687 for P := CurrentStation.MapStation.Passengers.Count - 1 downto 0 do begin
1688 if (Passengers.Count < TrainPassengerCount) then begin
1689 Passenger := TMetroPassenger(CurrentStation.MapStation.Passengers[P]);
1690 if CurrentStation.MapStation.IsBestStationForShape(Passenger.Shape,
1691 TargetStation, CurrentStation) then begin
1692 Passenger.Station := nil;
1693 CurrentStation.MapStation.Passengers.Delete(P);
1694 Passengers.Add(Passenger);
1695 Passenger.Train := TMetroTrain(Trains[I]);
1696 end;
1697 end else Break; // No more space
1698 end;
1699
1700 LastPosDelta := Abs(GetTargetStationDistance);
1701 InStation := False;
1702 LastTrainMoveTime := Time;
1703 end;
1704 end else begin
1705 PosChange := Direction + Trunc(Direction * TrainSpeed * (Time - LastTrainMoveTime));
1706 RelPos := RelPos + PosChange;
1707 LastTrainMoveTime := Time;
1708 Redraw;
1709 if Assigned(BaseTrackPoint) then
1710 while (Direction = -1) and (RelPos < 0) do begin
1711 if BaseTrackPoint <> TLineStation(Line.LineStations.First).TrackPoint then begin
1712 BaseTrackPoint := BaseTrackPoint.GetNeighDown;
1713 if Assigned(BaseTrackPoint) then
1714 RelPos := RelPos + BaseTrackPoint.GetDistance
1715 else begin
1716 BaseTrackPoint := TLineStation(Line.LineStations.First).TrackPoint;
1717 RelPos := 0;
1718 end;
1719 end else
1720 if Line.IsCircular then begin
1721 BaseTrackPoint := TLineStation(Line.LineStations.Last).TrackPoint;
1722 RelPos := RelPos + BaseTrackPoint.GetDistance;
1723 end else begin
1724 RelPos := 0;
1725 Break;
1726 end;
1727 end;
1728 if Assigned(BaseTrackPoint) then
1729 while (Direction = 1) and (RelPos > BaseTrackPoint.GetDistance) do begin
1730 if BaseTrackPoint <> TLineStation(Line.LineStations.Last).TrackPoint then begin
1731 RelPos := RelPos - BaseTrackPoint.GetDistance;
1732 BaseTrackPoint := BaseTrackPoint.GetNeighUp;
1733 if not Assigned(BaseTrackPoint) then begin
1734 BaseTrackPoint := TLineStation(Line.LineStations.Last).TrackPoint;
1735 RelPos := 0;
1736 end;
1737 end else
1738 if Line.IsCircular then begin
1739 RelPos := RelPos - BaseTrackPoint.GetDistance;
1740 BaseTrackPoint := TLineStation(Line.LineStations.First).TrackPoint;
1741 end else begin
1742 RelPos := BaseTrackPoint.GetDistance;
1743 Break;
1744 end;
1745 end;
1746 PosDelta := Abs(GetTargetStationDistance);
1747 if PosDelta >= LastPosDelta then begin
1748 // We are getting far from station, stop at station
1749 BaseTrackPoint := TargetStation.TrackPoint;
1750 RelPos := 0;
1751 InStation := True;
1752 StationStopTime := Time;
1753 Redraw;
1754 end;
1755 LastPosDelta := PosDelta;
1756 end;
1757 end;
1758 end;
1759end;
1760
1761function TEngine.GetUnusedLine: TMetroLine;
1762var
1763 I: Integer;
1764begin
1765 I := 0;
1766 while (I < Lines.Count) and (TMetroLine(Lines[I]).Track.Points.Count > 0) do Inc(I);
1767 if I < Lines.Count then Result := TMetroLine(Lines[I])
1768 else Result := nil;
1769end;
1770
1771procedure TEngine.ShiftTrackPoints;
1772var
1773 I: Integer;
1774 J: Integer;
1775 L: Integer;
1776 Link1, Link2: TPoint;
1777 NewPoint: TPoint;
1778begin
1779 // Reset all trackpoints position shift
1780 for I := 0 to Lines.Count - 1 do
1781 with TMetroLine(Lines[I]) do
1782 for J := 0 to Track.Points.Count - 1 do
1783 TTrackPoint(Track.Points[J]).Position := TTrackPoint(Track.Points[J]).PositionDesigned;
1784
1785 // Calculate new position shifts
1786 for I := 0 to Stations.Count - 1 do
1787 TMapStation(Stations[I]).ShiftTrackPoints;
1788
1789 // Compute track points from track shift
1790 for L := 0 to Lines.Count - 1 do
1791 with TMetroLine(Lines[L]) do begin
1792 if Track.Points.Count > 1 then begin
1793 TTrackPoint(Track.Points[0]).Position := AddPoint(TTrackPoint(Track.Points[0]).PositionDesigned,
1794 TTrackPoint(Track.Points[0]).LinkUp.Shift);
1795 end;
1796 for I := 1 to Track.Points.Count - 1 do
1797 with TTrackPoint(Track.Points[I]) do
1798 if Assigned(TTrackPoint(Track.Points[I]).LinkDown) and Assigned(TTrackPoint(Track.Points[I]).LinkUp) then begin
1799 Link1 := SubPoint(AddPoint(TTrackPoint(Track.Points[I]).PositionDesigned, TTrackPoint(Track.Points[I]).LinkDown.Shift),
1800 AddPoint(TTrackPoint(Track.Points[I - 1]).PositionDesigned, TTrackPoint(Track.Points[I]).LinkDown.Shift));
1801 if (I + 1) < Track.Points.Count then
1802 Link2 := SubPoint(AddPoint(TTrackPoint(Track.Points[I + 1]).PositionDesigned, TTrackPoint(Track.Points[I]).LinkUp.Shift),
1803 AddPoint(TTrackPoint(Track.Points[I]).PositionDesigned, TTrackPoint(Track.Points[I]).LinkUp.Shift))
1804 else Link2 := Link1;
1805
1806 if ArcTanPoint(Link1) = ArcTanPoint(Link2) then begin
1807 // Parallel lines
1808 NewPoint := AddPoint(TTrackPoint(Track.Points[I]).PositionDesigned,
1809 TTrackPoint(Track.Points[I]).LinkDown.Shift);
1810 TTrackPoint(Track.Points[I]).Position := NewPoint;
1811 end else begin
1812 // Intersected lines
1813 NewPoint := LineIntersect(AddPoint(TTrackPoint(Track.Points[I - 1]).PositionDesigned,
1814 TTrackPoint(Track.Points[I]).LinkDown.Shift),
1815 AddPoint(TTrackPoint(Track.Points[I]).PositionDesigned, TTrackPoint(Track.Points[I]).LinkDown.Shift),
1816 AddPoint(TTrackPoint(Track.Points[I]).PositionDesigned, TTrackPoint(Track.Points[I]).LinkUp.Shift),
1817 AddPoint(TTrackPoint(Track.Points[I + 1]).PositionDesigned, TTrackPoint(Track.Points[I]).LinkUp.Shift));
1818 TTrackPoint(Track.Points[I]).Position := NewPoint;
1819 end;
1820 end;
1821 end;
1822
1823 // Remove all temporal links
1824 for I := 0 to Lines.Count - 1 do
1825 with TMetroLine(Lines[I]) do
1826 for J := 0 to Track.Points.Count - 1 do
1827 if Assigned(TTrackPoint(Track.Points[J]).LinkUp) then begin
1828 TTrackPoint(Track.Points[J]).LinkUp.Free;
1829 TTrackPoint(Track.Points[J]).LinkUp := nil;
1830 TTrackPoint(Track.Points[J + 1]).LinkDown := nil;
1831 end;
1832end;
1833
1834procedure TEngine.DrawClock(Canvas: TCanvas);
1835var
1836 ClockCenter: TPoint;
1837 Angle: Double;
1838 Text: string;
1839 I: Integer;
1840const
1841 ClockSize = 20;
1842begin
1843 Canvas.Pen.Style := psSolid;
1844 Canvas.Pen.Color := clBlack;
1845 Canvas.Pen.Width := 2;
1846 ClockCenter := Point(Canvas.Width - 30, 40);
1847 Angle := Time / (12 * OneHour) * 2 * Pi - Pi / 2;
1848 Canvas.EllipseC(ClockCenter.X, ClockCenter.Y, ClockSize, ClockSize);
1849 Canvas.Line(ClockCenter, Point(ClockCenter.X + Round(Cos(Angle) * ClockSize * 0.8),
1850 ClockCenter.Y + Round(Sin(Angle) * ClockSize * 0.8)));
1851 Text := FormatDateTime('ddd', Time);
1852 Canvas.TextOut(ClockCenter.X - ClockSize - Canvas.TextWidth(Text) - 5, ClockCenter.Y -
1853 Canvas.TextWidth(Text) div 2, Text);
1854 for I := 0 to 12 do begin
1855 Angle := I / 12 * 2 * Pi;
1856 Canvas.Line(ClockCenter.X + Round(Cos(Angle) * ClockSize * 0.8),
1857 ClockCenter.Y + Round(Sin(Angle) * ClockSize * 0.8),
1858 ClockCenter.X + Round(Cos(Angle) * ClockSize * 0.9),
1859 ClockCenter.Y + Round(Sin(Angle) * ClockSize * 0.9));
1860 end;
1861end;
1862
1863procedure TEngine.DrawTrains(Canvas: TCanvas);
1864var
1865 I: Integer;
1866 P: Integer;
1867 Pos: TPoint;
1868 Points: array of TPoint;
1869 Angle: Double;
1870 ShapePos: TPoint;
1871begin
1872 // Draw trains
1873 for I := 0 to Trains.Count - 1 do
1874 with TMetroTrain(Trains[I]) do begin
1875 if Assigned(Line) then begin
1876 Canvas.Brush.Color := Line.Color;
1877 Canvas.Brush.Style := bsSolid;
1878 Canvas.Pen.Style := psClear;
1879 Pos := GetPosition;
1880 Angle := GetAngle;
1881
1882 SetLength(Points, 4);
1883 Points[0] := RotatePoint(Pos, Point(Pos.X - TrainSize div 2, Pos.Y - TrainSize div 3), Angle);
1884 Points[1] := RotatePoint(Pos, Point(Pos.X + TrainSize div 2, Pos.Y - TrainSize div 3), Angle);
1885 Points[2] := RotatePoint(Pos, Point(Pos.X + TrainSize div 2, Pos.Y + TrainSize div 3), Angle);
1886 Points[3] := RotatePoint(Pos, Point(Pos.X - TrainSize div 2, Pos.Y + TrainSize div 3), Angle);
1887 Canvas.Polygon(Points);
1888 Canvas.Brush.Color := clWhite;
1889 for P := 0 to Passengers.Count - 1 do
1890 with TMetroPassenger(Passengers[P]) do begin
1891 ShapePos := Point(Pos.X - Trunc(TrainSize div 3 * 1) + (P mod 3) * TrainSize div 3,
1892 Pos.Y - Trunc(TrainSize div 6 * 1) + (P div 3) * TrainSize div 3);
1893 ShapePos := RotatePoint(Pos, ShapePos, Angle);
1894 DrawShape(Canvas, ShapePos, Shape, TrainSize div 3, Angle + Pi / 2);
1895 end;
1896 end;
1897 end;
1898end;
1899
1900procedure TEngine.Tick;
1901var
1902 Passenger: TMetroPassenger;
1903 I: Integer;
1904begin
1905 if State = gsRunning then begin
1906 FTime := FTime + (Now - LastTickTime) / OneSecond * TimePerSecond;
1907 Redraw; // Redraw on every because engine time is changed so clock should be redrawn
1908
1909 // Add new trains
1910 if (Time - LastNewWeekTime) > NewTrainPeriod then begin
1911 LastNewWeekTime := Time;
1912 Trains.AddNew;
1913 // TODO: Show notification screen with confirmation
1914 Redraw;
1915 end;
1916
1917 // Add new shape
1918 if (Time - LastNewShapeTime) > NewShapePeriod then begin
1919 LastNewShapeTime := Time;
1920 if ShapeCount <= Integer(High(TStationShape)) then Inc(ShapeCount);
1921 Redraw;
1922 end;
1923
1924 // Add new stations
1925 if (Time - LastNewStationTime) > NewStationPeriod then begin
1926 LastNewStationTime := Time;
1927 Stations.AddNew;
1928 ResizeView;
1929 Redraw;
1930 end;
1931
1932 // Add new passengers
1933 if (Time - LastNewPassengerTime) > NewPassengerPeriod then begin
1934 LastNewPassengerTime := Time;
1935 for I := 0 to Stations.Count - 1 do
1936 with TMapStation(Stations[I]) do
1937 if Random < NewPassengerProbability then begin
1938 Passenger := Self.Passengers.AddNew;
1939 Passenger.Station := TMapStation(Stations[I]);
1940 Passengers.Add(Passenger);
1941
1942 // Passenger is not allowed to have same shape
1943 while (Passenger.Shape = Passenger.Station.Shape) or not (Passenger.Shape in GetExistStationShapes) do
1944 Passenger.Shape := TStationShape((Integer(Passenger.Shape) + 1) mod Integer(ShapeCount));
1945 Redraw;
1946 end;
1947 end;
1948
1949 // Check station passenger overload state
1950 for I := 0 to Stations.Count - 1 do
1951 with TMapStation(Stations[I]) do begin
1952 if Passengers.Count > MaxWaitingPassengers then begin
1953 OverloadDuration := OverloadDuration + (FTime - FLastTime);
1954 if OverloadDuration > MaxPassengersOveloadTime then
1955 OverloadDuration := MaxPassengersOveloadTime;
1956 if OverloadDuration < MaxPassengersOveloadTime then Redraw;
1957 end;
1958 if Passengers.Count <= MaxWaitingPassengers then begin
1959 if OverloadDuration > 0 then Redraw;
1960 OverloadDuration := OverloadDuration - (FTime - FLastTime);
1961 if OverloadDuration < 0 then begin
1962 OverloadDuration := 0;
1963 end;
1964 end;
1965 end;
1966
1967 TrainMovement;
1968
1969 // Game over
1970 for I := 0 to Stations.Count - 1 do
1971 with TMapStation(Stations[I]) do begin
1972 if OverloadDuration >= MaxPassengersOveloadTime then begin
1973 State := gsGameOver;
1974 Redraw;
1975 end;
1976 end;
1977
1978 end;
1979 LastTickTime := Now;
1980 FLastTime := FTime;
1981end;
1982
1983procedure TEngine.MouseMove(Position: TPoint);
1984var
1985 FocusedStation: TMapStation;
1986 Line: TMetroLine;
1987 LineStationDown: TLineStation;
1988 LineStationUp: TLineStation;
1989 CurrentTrackPoint: TTrackPoint;
1990begin
1991 LastMousePos := Position;
1992 if MouseHold then begin
1993 FocusedStation := GetStationOnPos(View.PointDestToSrc(Position));
1994 Line := nil;
1995 if Assigned(TrackStationDown) then begin
1996 Line := TrackStationDown.Track.Line;
1997 Redraw;
1998 end;
1999 if Assigned(TrackStationUp) then begin
2000 Line := TrackStationUp.Track.Line;
2001 Redraw;
2002 end;
2003 if Assigned(Line) and not Assigned(LastFocusedStation) and Assigned(FocusedStation) then begin
2004 if Assigned(TrackStationDown) and (TrackStationDown.LineStation.MapStation = FocusedStation) then begin
2005 // Disconnect down
2006 CurrentTrackPoint := TrackStationDown;
2007 TrackStationDown := TrackStationDown.GetDown;
2008 Line.DisconnectStation(CurrentTrackPoint.LineStation);
2009 end else
2010 if Assigned(TrackStationUp) and (TrackStationUp.LineStation.MapStation = FocusedStation) then begin
2011 // Disconnect up
2012 CurrentTrackPoint := TrackStationUp;
2013 if Assigned(TrackStationUp) then
2014 TrackStationUp := TrackStationUp.GetUp;
2015 Line.DisconnectStation(CurrentTrackPoint.LineStation);
2016 end else
2017 if Assigned(Line) and ((not Line.IsCircular) or ((TrackStationDown <> nil) and (TrackStationUp <> nil))) and
2018 ((Line.LineStations.SearchMapStation(FocusedStation) = nil) or
2019 ((Line.LineStations.Count > 0) and
2020 ((TLineStation(Line.LineStations.First).MapStation = FocusedStation) or
2021 (TLineStation(Line.LineStations.Last).MapStation = FocusedStation)) and
2022 ((TrackStationDown = nil) or (TrackStationUp = nil)) and
2023 (not Line.IsCircular))) then begin
2024 if Assigned(TrackStationDown) then LineStationDown := TrackStationDown.LineStation
2025 else LineStationDown := nil;
2026 if Assigned(TrackStationUp) then LineStationUp := TrackStationUp.LineStation
2027 else LineStationUp := nil;
2028 Line.ConnectStation(FocusedStation, LineStationDown, LineStationUp);
2029 if Assigned(TrackStationDown) then TrackStationDown := TrackStationDown.GetUp
2030 else if Assigned(TrackStationUp) then TrackStationUp := TrackStationUp.GetDown;
2031 end;
2032 end;
2033 LastFocusedStation := FocusedStation;
2034 end;
2035end;
2036
2037procedure TEngine.MouseUp(Button: TMouseButton; Position: TPoint);
2038var
2039 I: Integer;
2040 FocusedTrack: TTrackLink;
2041begin
2042 if Button = mbLeft then begin
2043 // Place selected train if focused track
2044 if Assigned(SelectedTrain) then begin
2045 SelectedTrain.TargetStation := nil;
2046 SelectedTrain.BaseTrackPoint := nil;
2047 if Assigned(SelectedTrain.Line) then begin
2048 SelectedTrain.Line.Trains.Remove(SelectedTrain);
2049 SelectedTrain.Line := nil;
2050 end;
2051 FocusedTrack := GetTrackOnPos(View.PointDestToSrc(Position));
2052 if Assigned(FocusedTrack.Points[0]) then begin
2053 SelectedTrain.Line := TTrackPoint(FocusedTrack.Points[0]).Track.Line;
2054 SelectedTrain.Line.Trains.Add(SelectedTrain);
2055 SelectedTrain.BaseTrackPoint := TTrackPoint(FocusedTrack.Points[0]);
2056 end else
2057 if Assigned(FocusedTrack.Points[1]) then begin
2058 SelectedTrain.Line := TTrackPoint(FocusedTrack.Points[1]).Track.Line;
2059 SelectedTrain.Line.Trains.Add(SelectedTrain);
2060 SelectedTrain.BaseTrackPoint := TTrackPoint(FocusedTrack.Points[1]);
2061 end;
2062 FocusedTrack.Free;
2063 end;
2064
2065 // Line color selection
2066 for I := 0 to Lines.Count - 1 do
2067 if Distance(Point(View.DestRect.Right div 2 - Length(LineColors) div 2 * LineColorsDist + I * LineColorsDist,
2068 View.DestRect.Bottom - LineColorsDist), Position) < 20 then begin
2069 SelectedLine := TMetroLine(Lines[I]);
2070 Exit;
2071 end;
2072
2073 // Remove single line station on line
2074 if Assigned(TrackStationDown) and (TrackStationDown.Track.Line.LineStations.Count = 1) then begin
2075 TrackStationDown.Track.Line.DisconnectStation(TLineStation(TrackStationDown.Track.Line.LineStations.First));
2076 end;
2077 if Assigned(TrackStationUp) and (TrackStationUp.Track.Line.LineStations.Count = 1) then begin
2078 TrackStationUp.Track.Line.DisconnectStation(TLineStation(TrackStationUp.Track.Line.LineStations.First));
2079 end;
2080 end else
2081 if Button = mbRight then begin
2082 SelectedLine := nil;
2083 end;
2084 MouseHold := False;
2085 TrackStationDown := nil;
2086 TrackStationUp := nil;
2087 SelectedTrain := nil;
2088end;
2089
2090procedure TEngine.MouseDown(Button: TMouseButton; Position: TPoint);
2091var
2092 Station: TMapStation;
2093 NewLine: TMetroLine;
2094 Track: TTrackLink;
2095 NewIndex: Integer;
2096begin
2097 if Button = mbLeft then begin
2098 MouseHold := True;
2099 LastFocusedStation := nil;
2100
2101 // Train selection
2102 SelectedTrain := GetTrainOnPos(View.PointDestToSrc(Position));
2103 if Assigned(SelectedTrain) then begin
2104 Exit;
2105 end;
2106
2107 // Select unused train
2108 if (Distance(Position, Point(View.DestRect.Right div 2 - Length(LineColors) div 2 * LineColorsDist - 100,
2109 View.DestRect.Bottom - LineColorsDist)) < 30) and
2110 (Trains.GetUnusedCount > 0) then begin
2111 SelectedTrain := Trains.GetUnusedTrain;
2112 Exit;
2113 end;
2114
2115 // Line selection
2116 Track := GetTrackOnPos(View.PointDestToSrc(Position));
2117 if Assigned(Track) and Assigned(Track.Points[0]) and Assigned(Track.Points[1]) then begin
2118 SelectedLine := TTrackPoint(Track.Points[0]).Track.Line;
2119
2120 TrackStationDown := TTrackPoint(Track.Points[0]);
2121 NewIndex := TrackStationDown.Track.Points.IndexOf(TrackStationDown);
2122 while Assigned(TrackStationDown) and (not Assigned(TrackStationDown.LineStation)) do begin
2123 NewIndex := NewIndex - 1;
2124 if NewIndex >= 0 then TrackStationDown := TTrackPoint(TrackStationDown.Track.Points[NewIndex])
2125 else TrackStationDown := nil;
2126 end;
2127 TrackStationUp := TTrackPoint(Track.Points[1]);
2128 NewIndex := TrackStationUp.Track.Points.IndexOf(TrackStationDown);
2129 while Assigned(TrackStationUp) and (not Assigned(TrackStationUp.LineStation)) do begin
2130 NewIndex := NewIndex + 1;
2131 if NewIndex < TrackStationUp.Track.Points.Count then
2132 TrackStationUp := TTrackPoint(TrackStationUp.Track.Points[NewIndex])
2133 else TrackStationUp := nil;
2134 end;
2135 Track.Free;
2136 Exit;
2137 end;
2138 if Assigned(Track) then Track.Free;
2139
2140 // New track creation from selected station as start
2141 Station := GetStationOnPos(View.PointDestToSrc(Position));
2142 if Assigned(Station) then begin
2143 if Assigned(SelectedLine) and (SelectedLine.LineStations.Count = 0) then NewLine := SelectedLine
2144 else NewLine := GetUnusedLine;
2145 if Assigned(NewLine) then begin
2146 NewLine.ConnectStation(Station, nil, nil);
2147 TrackStationDown := TTrackPoint(NewLine.Track.Points.Last);
2148 TrackStationUp := nil;
2149 LastFocusedStation := Station;
2150 end;
2151 end;
2152 end;
2153end;
2154
2155procedure TEngine.Reset;
2156var
2157 NewTrain: TMetroTrain;
2158 I: Integer;
2159 NewStation: TMapStation;
2160 InitialStationCount: Integer;
2161begin
2162 Passengers.Clear;
2163 Lines.Clear;
2164 Stations.Clear;
2165
2166 ShapeCount := 3;
2167 ServedPassengerCount := 0;
2168
2169 // Start with 3 stations with each different shape
2170 InitialStationCount := 3;
2171 for I := 0 to InitialStationCount - 1 do begin
2172 NewStation := Stations.AddNew;
2173 if I = 0 then NewStation.Shape := ssSquare
2174 else if I = 1 then NewStation.Shape := ssCircle
2175 else if I = 2 then NewStation.Shape := ssTriangle;
2176 end;
2177
2178 for I := 0 to 8 do begin
2179 Lines.AddNew;
2180 NewTrain := TMetroTrain.Create;
2181 Trains.Add(NewTrain);
2182 end;
2183
2184 ResizeView;
2185
2186 SelectedLine := nil;
2187 FTime := 0;
2188 FLastTime := 0;
2189 LastNewStationTime := Time;
2190 LastNewPassengerTime := Time;
2191 LastNewWeekTime := Time;
2192 LastNewShapeTime := Time;
2193 LastTickTime := Now;
2194 State := gsRunning;
2195 Redraw;
2196end;
2197
2198procedure TEngine.Redraw;
2199begin
2200 RedrawPending := True;
2201end;
2202
2203constructor TEngine.Create;
2204begin
2205 Stations := TMapStations.Create;
2206 Stations.Engine := Self;
2207 Lines := TMetroLines.Create;
2208 Lines.Engine := Self;
2209 Passengers := TMetroPassengers.Create;
2210 Passengers.Engine := Self;
2211 Map := TMap.Create;
2212 View := TView.Create;
2213 Trains := TMetroTrains.Create;
2214 ImagePassenger := TImage.Create(nil);
2215 ImageLocomotive := TImage.Create(nil);
2216 //if FileExists(ImagePassengerName) then
2217 // ImagePassenger.Picture.LoadFromFile(ImagePassengerName);
2218 //if FileExists(ImageLocomotiveName) then
2219 // ImageLocomotive.Picture.LoadFromFile(ImageLocomotiveName);
2220 MetaCanvas := TMetaCanvas.Create;
2221end;
2222
2223destructor TEngine.Destroy;
2224begin
2225 MetaCanvas.Free;
2226 Trains.Free;
2227 ImageLocomotive.Free;
2228 ImagePassenger.Free;
2229 View.Free;
2230 Map.Free;
2231 Passengers.Free;
2232 Stations.Free;
2233 Lines.Free;
2234 inherited Destroy;
2235end;
2236
2237procedure TEngine.Paint(TargetCanvas: TCanvas);
2238var
2239 I: Integer;
2240 S: Integer;
2241 Size: Integer;
2242 P: Integer;
2243 Pos: TPoint;
2244 Text: string;
2245 Angle: Double;
2246 PassengerPos: TPoint;
2247 Direction: Integer;
2248 Points: array of TPoint;
2249 Canvas: TMetaCanvas;
2250const
2251 GameOverText = 'Game Over';
2252 GameOverReason = 'Overcrowding at this station has forced you to resign as metro manager.';
2253 GameOverStatistic = '%d passengers travelled on your metro over %d days.';
2254begin
2255 Canvas := MetaCanvas;
2256 Canvas.SetSize(Point(TargetCanvas.Width, TargetCanvas.Height));
2257 Canvas.Reset;
2258
2259 // Draw station passenger overload
2260 for I := 0 to Stations.Count - 1 do
2261 with TMapStation(Stations[I]) do begin
2262 if OverloadDuration > 0 then begin
2263 Canvas.Brush.Color := clSilver;
2264 Canvas.Brush.Style := bsSolid;
2265 Canvas.Pen.Color := clSilver;
2266 Canvas.Pen.Style := psSolid;
2267 Angle := OverloadDuration / MaxPassengersOveloadTime * 2 * Pi;
2268 Canvas.Pie(Position.X - StationOverloadSize, Position.Y - StationOverloadSize,
2269 Position.X + StationOverloadSize, Position.Y + StationOverloadSize,
2270 Trunc(Position.X + StationOverloadSize * Cos(Angle)),
2271 Trunc(Position.Y + StationOverloadSize * Sin(Angle)), Position.X + StationOverloadSize, Position.Y);
2272 end;
2273 end;
2274
2275 // Draw lines
2276 for I := 0 to Lines.Count - 1 do
2277 with TMetroLine(Lines[I]) do begin
2278 Canvas.Pen.Color := Color;
2279 Canvas.Pen.Style := psSolid;
2280 Canvas.Pen.Width := MetroLineThickness;
2281 if Track.Points.Count > 0 then Canvas.MoveTo(TTrackPoint(Track.Points[0]).Position);
2282 for S := 1 to Track.Points.Count - 1 do begin
2283 Canvas.LineTo(TTrackPoint(Track.Points[S]).Position);
2284{ if (S = TrackPoints.Count - 1) then begin
2285 Canvas.Pen.EndCap := pecSquare;
2286 Angle := arctan2((TTrackPoint(TrackPoints[S]).Position.Y - TTrackPoint(TrackPoints[S - 1]).Position.Y),
2287 (TTrackPoint(TrackPoints[S]).Position.X - TTrackPoint(TrackPoints[S - 1]).Position.X));
2288 EndPoint := Point(Round(TTrackPoint(TrackPoints[S]).Position.X + EndStationLength * Cos(Angle)),
2289 Round(TTrackPoint(TrackPoints[S]).Position.Y + EndStationLength * Sin(Angle)));
2290 Canvas.LineTo(EndPoint);
2291 Canvas.MoveTo(Point(Round(EndPoint.X + Cos(Angle + Pi / 2) * EndStationLength / 3),
2292 Round(EndPoint.Y + Sin(Angle + Pi / 2) * EndStationLength / 3)));
2293 Canvas.LineTo(Point(Round(EndPoint.X + Cos(Angle - Pi / 2) * EndStationLength / 3),
2294 Round(EndPoint.Y + Sin(Angle - Pi / 2) * EndStationLength / 3)));
2295 Canvas.Pen.EndCap := pecRound;
2296 end;}
2297 end;
2298(* Canvas.Pen.Color := Color;
2299 Canvas.Pen.Style := psSolid;
2300 Canvas.Pen.Width := MetroLineThickness div 2;
2301 if Track.Points.Count > 0 then Canvas.MoveTo(TTrackPoint(Track.Points[0]).PositionDesigned);
2302 for S := 1 to Track.Points.Count - 1 do begin
2303 Canvas.LineTo(TTrackPoint(Track.Points[S]).PositionDesigned);
2304{ if (S = TrackPoints.Count - 1) then begin
2305 Canvas.Pen.EndCap := pecSquare;
2306 Angle := arctan2((TTrackPoint(TrackPoints[S]).Position.Y - TTrackPoint(TrackPoints[S - 1]).Position.Y),
2307 (TTrackPoint(TrackPoints[S]).Position.X - TTrackPoint(TrackPoints[S - 1]).Position.X));
2308 EndPoint := Point(Round(TTrackPoint(TrackPoints[S]).Position.X + EndStationLength * Cos(Angle)),
2309 Round(TTrackPoint(TrackPoints[S]).Position.Y + EndStationLength * Sin(Angle)));
2310 Canvas.LineTo(EndPoint);
2311 Canvas.MoveTo(Point(Round(EndPoint.X + Cos(Angle + Pi / 2) * EndStationLength / 3),
2312 Round(EndPoint.Y + Sin(Angle + Pi / 2) * EndStationLength / 3)));
2313 Canvas.LineTo(Point(Round(EndPoint.X + Cos(Angle - Pi / 2) * EndStationLength / 3),
2314 Round(EndPoint.Y + Sin(Angle - Pi / 2) * EndStationLength / 3)));
2315 Canvas.Pen.EndCap := pecRound;
2316 end;}
2317 end;
2318 {
2319 if (TrackPoints.Count > 1) then begin
2320 Canvas.Pen.EndCap := pecSquare;
2321 Angle := arctan2((TTrackPoint(TrackPoints[1]).Position.Y - TTrackPoint(TrackPoints[0]).Position.Y),
2322 (TTrackPoint(TrackPoints[1]).Position.X - TTrackPoint(TrackPoints[0]).Position.X));
2323 Canvas.MoveTo(TTrackPoint(TrackPoints[0]).Position);
2324 EndPoint := Point(Round(TTrackPoint(TrackPoints[0]).Position.X - EndStationLength * Cos(Angle)),
2325 Round(TTrackPoint(TrackPoints[0]).Position.Y - EndStationLength * Sin(Angle)));
2326 Canvas.LineTo(EndPoint);
2327 Canvas.MoveTo(Point(Round(EndPoint.X - Cos(Angle + Pi / 2) * EndStationLength / 3),
2328 Round(EndPoint.Y - Sin(Angle + Pi / 2) * EndStationLength / 3)));
2329 Canvas.LineTo(Point(Round(EndPoint.X - Cos(Angle - Pi / 2) * EndStationLength / 3),
2330 Round(EndPoint.Y - Sin(Angle - Pi / 2) * EndStationLength / 3)));
2331 Canvas.Pen.EndCap := pecRound;
2332 end; }
2333 *)
2334 end;
2335 // Draw design time lines
2336 if Assigned(TrackStationDown) and Assigned(TrackStationDown.LineStation) then begin
2337 Canvas.Pen.Color := TrackStationDown.Track.Line.Color;
2338 Canvas.MoveTo(TrackStationDown.LineStation.TrackPoint.Position);
2339 DrawLine(Canvas, View.PointDestToSrc(LastMousePos));
2340 end;
2341 if Assigned(TrackStationUp) and Assigned(TrackStationUp.LineStation) then begin
2342 Canvas.Pen.Color := TrackStationUp.Track.Line.Color;
2343 Canvas.MoveTo(TrackStationUp.LineStation.TrackPoint.Position);
2344 DrawLine(Canvas, View.PointDestToSrc(LastMousePos));
2345 end;
2346
2347 DrawTrains(Canvas);
2348
2349 // Draw stations
2350 Canvas.Pen.Width := 5;
2351 for I := 0 to Stations.Count - 1 do
2352 with TMapStation(Stations[I]) do begin
2353 Canvas.Pen.Style := psSolid;
2354 if Assigned(SelectedLine) and (Lines.IndexOf(SelectedLine) <> -1) then begin
2355 Canvas.Brush.Style := bsClear;
2356 Canvas.Pen.Color := SelectedLine.Color;
2357 DrawShape(Canvas, Position, Shape, StationSize + Canvas.Pen.Width + 4, 0);
2358 end;
2359
2360 Canvas.Brush.Color := clWhite;
2361 Canvas.Brush.Style := bsSolid;
2362 Canvas.Pen.Color := clBlack;
2363 DrawShape(Canvas, Position, Shape, StationSize, 0);
2364
2365 // Draw passengers
2366 Canvas.Pen.Style := psClear;
2367 Canvas.Brush.Color := clBlack;
2368 PassengerPos := Point(0, 0);
2369 Direction := 1;
2370 for P := 0 to Passengers.Count - 1 do
2371 with TMetroPassenger(Passengers[P]) do begin
2372 DrawShape(Canvas, Point(Position.X + StationSize + PassengerPos.X,
2373 Position.Y - StationSize div 2 + PassengerPos.Y),
2374 Shape, PassengerSize, 0);
2375 PassengerPos := Point(PassengerPos.X + Direction * (PassengerSize + 2), PassengerPos.Y);
2376 if PassengerPos.X >= (PassengerSize + 2) * VisiblePassengersPerLine then begin
2377 Direction := -Direction;
2378 PassengerPos.X := PassengerPos.X - (PassengerSize + 2);
2379 PassengerPos.Y := PassengerPos.Y + (PassengerSize + 2);
2380 end;
2381 if PassengerPos.X < 0 then begin
2382 Direction := -Direction;
2383 PassengerPos.X := 0;
2384 PassengerPos.Y := PassengerPos.Y + (PassengerSize + 2);
2385 end;
2386 end;
2387
2388{ if ShowDistances then begin
2389 Canvas.Brush.Style := bsClear;
2390 Text := '';
2391 for P := 0 to 5 do
2392 Text := Text + IntToStr(ShapeDistance[TStationShape(P)]) + ',';
2393 Canvas.TextOut(Position.X + StationSize div 2, Position.Y + StationSize div 2, Text);
2394 end;
2395 }
2396 end;
2397
2398 // Clear background
2399 TargetCanvas.Brush.Color := $eff0e0;
2400 TargetCanvas.Brush.Style := bsSolid;
2401 TargetCanvas.Clear;
2402
2403 MetaCanvas.Move(Point(-View.SourceRect.Left, -View.SourceRect.Top));
2404 MetaCanvas.Zoom(View.Zoom);
2405
2406 // Draw meta canvas to real target canvas
2407 MetaCanvas.DrawTo(TargetCanvas);
2408
2409 // Line selection
2410 TargetCanvas.Pen.Width := 4;
2411 for I := 0 to High(LineColors) do begin
2412 if Assigned(Lines.SearchByColor(LineColors[I])) then begin
2413 TargetCanvas.Brush.Color := LineColors[I];
2414 Size := 15;
2415 end else begin
2416 TargetCanvas.Brush.Color := clSilver;
2417 Size := 5;
2418 end;
2419 TargetCanvas.Pen.Color := clBlack;
2420 if Assigned(SelectedLine) and (SelectedLine.Color = LineColors[I]) then begin
2421 TargetCanvas.Pen.Style := psSolid;
2422 end else begin
2423 TargetCanvas.Pen.Style := psClear;
2424 end;
2425
2426 TargetCanvas.EllipseC(TargetCanvas.Width div 2 - Length(LineColors) div 2 * LineColorsDist + I * LineColorsDist,
2427 TargetCanvas.Height - LineColorsDist, Size, Size);
2428 end;
2429
2430 // Draw unused trains
2431 Text := IntToStr(Trains.GetUnusedCount);
2432 TargetCanvas.Draw(Canvas.Width div 2 - Length(LineColors) div 2 * LineColorsDist - 100,
2433 TargetCanvas.Height - LineColorsDist - ImageLocomotive.Picture.Bitmap.Height div 2, ImageLocomotive.Picture.Bitmap);
2434 TargetCanvas.Brush.Style := bsClear;
2435 TargetCanvas.Font.Size := 14;
2436 TargetCanvas.Font.Color := clBlack;
2437 TargetCanvas.TextOut(TargetCanvas.Width div 2 - Length(LineColors) div 2 * LineColorsDist - 50 - TargetCanvas.TextWidth(Text),
2438 TargetCanvas.Height - LineColorsDist - TargetCanvas.TextHeight(Text) div 2, Text);
2439
2440 // Status interface
2441 Text := IntToStr(ServedPassengerCount);
2442 TargetCanvas.Draw(TargetCanvas.Width - 50, TargetCanvas.Height - 60, ImagePassenger.Picture.Bitmap);
2443 TargetCanvas.Brush.Style := bsClear;
2444 TargetCanvas.Font.Size := 14;
2445 TargetCanvas.Font.Color := clBlack;
2446 TargetCanvas.TextOut(TargetCanvas.Width - 70 - TargetCanvas.TextWidth(Text), TargetCanvas.Height - 55, Text);
2447
2448 DrawClock(TargetCanvas);
2449
2450 // Show grabbed train by mouse
2451 if Assigned(SelectedTrain) then begin
2452 TargetCanvas.Brush.Color := clBlack; //SelectedTrain.Line.Color;
2453 TargetCanvas.Brush.Style := bsSolid;
2454 TargetCanvas.Pen.Style := psClear;
2455 Pos := LastMousePos;
2456 Angle := 0;
2457
2458 SetLength(Points, 4);
2459 Points[0] := RotatePoint(Pos, Point(Pos.X - TrainSize div 2, Pos.Y - TrainSize div 3), Angle);
2460 Points[1] := RotatePoint(Pos, Point(Pos.X + TrainSize div 2, Pos.Y - TrainSize div 3), Angle);
2461 Points[2] := RotatePoint(Pos, Point(Pos.X + TrainSize div 2, Pos.Y + TrainSize div 3), Angle);
2462 Points[3] := RotatePoint(Pos, Point(Pos.X - TrainSize div 2, Pos.Y + TrainSize div 3), Angle);
2463 TargetCanvas.Polygon(Points);
2464 end;
2465
2466 // Game over
2467 if State = gsGameOver then
2468 begin
2469 TargetCanvas.Font.Size := 40;
2470 TargetCanvas.Font.Color := clBlack;
2471 TargetCanvas.TextOut((TargetCanvas.Width - TargetCanvas.TextWidth(GameOverText)) div 2, 100, GameOverText);
2472 TargetCanvas.Font.Size := 14;
2473 TargetCanvas.TextOut((TargetCanvas.Width - TargetCanvas.TextWidth(GameOverReason)) div 2, 160, GameOverReason);
2474 Text := Format(GameOverStatistic, [ServedPassengerCount, Trunc(Time)]);
2475 TargetCanvas.TextOut((TargetCanvas.Width - TargetCanvas.TextWidth(Text)) div 2, 180, Text);
2476 end;
2477 RedrawPending := False;
2478end;
2479
2480end.
2481
Note: See TracBrowser for help on using the repository browser.