Browse Source

Out of control trains!

Tyler Hallada 6 years ago
parent
commit
4b7bf5353c
4 changed files with 96 additions and 28 deletions
  1. 5 22
      src/Line.ts
  2. 24 0
      src/Station.ts
  3. 51 6
      src/transport.ts
  4. 16 0
      src/utils.ts

+ 5 - 22
src/Line.ts

@@ -1,37 +1,20 @@
1 1
 import Station from './Station';
2 2
 import { distance, randomInt, randomPoint } from './utils';
3 3
 
4
-const largestStation = (stations: Station[]): Station => {
5
-  let largest: Station = null;
6
-  for (const station of stations) {
7
-    if (largest === null || station.population > largest.population) {
8
-      largest = station;
9
-    }
10
-  }
11
-  return largest;
12
-};
13
-
14
-const stationsWithinRadius = (stations: Station[], point: PIXI.Point,
15
-                              radius: number): Station[] => (
16
-  stations.filter(station => distance(point, station.location) <= radius)
17
-);
18
-
19
-const closestStations = (stations: Station[], point: PIXI.Point, num: number): Station[] => {
20
-  // bleh, i'm done
21
-  return stations;
22
-};
23
-
24 4
 export default class Line {
25 5
   public stations: Station[];
26 6
 
27 7
   constructor(stations: Station[], numStations: number) {
28 8
     this.stations = [];
29 9
     let stationsLeft = stations;
30
-    let largest = largestStation(stationsLeft);
10
+    let largest = Station.largestStation(stationsLeft);
31 11
     stationsLeft = stationsLeft.filter(s => s !== largest);
32 12
     this.stations.push(largest);
13
+
33 14
     while (this.stations.length < numStations) {
34
-      largest = largestStation(stationsWithinRadius(stationsLeft, largest.location, 500));
15
+      largest = Station.largestStation(
16
+        Station.stationsWithinRadius(stationsLeft, largest.location, 500),
17
+      );
35 18
       if (largest === null) {
36 19
         break;
37 20
       }

+ 24 - 0
src/Station.ts

@@ -1,4 +1,28 @@
1
+import { distance } from './utils';
2
+
1 3
 export default class Station {
4
+  // Utility methods for working with arrays of Stations
5
+  public static largestStation(stations: Station[]): Station {
6
+    let largest: Station = null;
7
+    for (const station of stations) {
8
+      if (largest === null || station.population > largest.population) {
9
+        largest = station;
10
+      }
11
+    }
12
+    return largest;
13
+  }
14
+
15
+  public static stationsWithinRadius(stations: Station[], point: PIXI.Point,
16
+                                     radius: number): Station[] {
17
+    return stations.filter(station => distance(point, station.location) <= radius);
18
+  }
19
+
20
+  public static closestStation(stations: Station[], point: PIXI.Point, num: number): Station {
21
+    return stations.reduce(
22
+      (prev, curr) => distance(point, prev.location) > distance(point, curr.location) ? prev : curr,
23
+    );
24
+  }
25
+
2 26
   public location: PIXI.Point;
3 27
   public population: number;
4 28
   public connections: Station[];

+ 51 - 6
src/transport.ts

@@ -2,10 +2,13 @@ import * as PIXI from 'pixi.js';
2 2
 import Line from './Line';
3 3
 import Station from './Station';
4 4
 import Train from './Train';
5
-import { distance, randomInt, randomPoint } from './utils';
5
+import { distance, pointsEqual, randomInt, randomPoint, weightedRandom } from './utils';
6 6
 
7 7
 import './style.css';
8 8
 
9
+const maxSpeed = 10.0;
10
+const acceleration = 0.25;
11
+
9 12
 const isPointDistant = (point: PIXI.Point, stations: Station[], minDistance: number): boolean => {
10 13
   for (const station of stations) {
11 14
     if (distance(point, station.location) < minDistance) {
@@ -45,12 +48,53 @@ const initTrains = (numTrains: number, stations: Station[]): Train[] => {
45 48
   const trains = [];
46 49
   for (let i = 0; i < numTrains; i += 1) {
47 50
     const originStation = stations[Math.floor(Math.random() * stations.length)];
48
-    const destStation = stations[Math.floor(Math.random() * stations.length)];
49
-    trains.push(new Train(originStation.location, 0, 0, originStation, destStation));
51
+    // const destStation = stations[Math.floor(Math.random() * stations.length)];
52
+    trains.push(new Train(originStation.location, 0, 0, originStation, undefined));
50 53
   }
51 54
   return trains;
52 55
 };
53 56
 
57
+const moveTrains = (trains: Train[], stations: Station[]) => {
58
+  for (const train of trains) {
59
+    // choose a destination randomly with a bias towards larger stations
60
+    if (train.destination === undefined) {
61
+      const closeStations = Station.stationsWithinRadius(stations, train.location, 500);
62
+      const closeStationWeights = closeStations.map(station => station.population);
63
+      train.destination = weightedRandom(closeStations, closeStationWeights);
64
+    }
65
+
66
+    // train reached destination, stop moving.
67
+    if (pointsEqual(train.location, train.destination.location)) {
68
+      train.speed = 0;
69
+      continue;
70
+    }
71
+
72
+    const journeyLeft = distance(train.location, train.destination.location);
73
+    // speeding up
74
+    if (train.speed < maxSpeed) {
75
+      train.speed += acceleration;
76
+    }
77
+
78
+    // slowing down
79
+    if ((train.speed / acceleration) >= (journeyLeft / train.speed)) {
80
+      train.speed -= acceleration;
81
+    }
82
+
83
+    // advance train
84
+    const progress = train.speed / journeyLeft;
85
+    train.location = new PIXI.Point(
86
+      train.location.x + (Math.abs(train.location.x - train.destination.location.x) * progress),
87
+      train.location.y + (Math.abs(train.location.y - train.destination.location.y) * progress),
88
+    );
89
+  }
90
+};
91
+
92
+const drawTrains = (trains: Train[], graphics: PIXI.Graphics) => {
93
+  for (const train of trains) {
94
+    graphics.drawCircle(train.location.x, train.location.y, 2);
95
+  }
96
+};
97
+
54 98
 const drawLine = (line: Line, graphics: PIXI.Graphics) => {
55 99
   const start = line.stations[0].location;
56 100
   graphics.moveTo(start.x, start.y);
@@ -76,21 +120,22 @@ const run = () => {
76 120
   // make these const
77 121
   let stations = initStations(30);
78 122
   let trains = initTrains(15, stations);
79
-  let line = new Line(stations, 10);
123
+  // let line = new Line(stations, 10);
80 124
 
81 125
   stations = initStations(30);
82 126
   trains = initTrains(15, stations);
83
-  line = new Line(stations, 10);
84 127
 
85 128
   ticker.stop();
86 129
   ticker.add((deltaTime) => {
130
+    moveTrains(trains, stations);
87 131
 
88 132
     graphics.clear();
89 133
     fpsText.text = `${Math.round(ticker.FPS)}`;
90 134
     graphics.lineStyle(1, 0xaeaeae, 1);
91 135
 
92 136
     drawStations(stations, graphics);
93
-    drawLine(line, graphics);
137
+    drawTrains(trains, graphics);
138
+    // drawLine(line, graphics);
94 139
   });
95 140
   ticker.start();
96 141
 

+ 16 - 0
src/utils.ts

@@ -5,10 +5,26 @@ export const randomInt = (min: number, max: number): number => (
5 5
   Math.floor(Math.random() * (max - (min + 1))) + min
6 6
 );
7 7
 
8
+export const weightedRandom = (choices: any[], weights: number[]): any => {
9
+  const totalWeight = weights.reduce((a, b) => a + b, 0);
10
+  const rand = randomInt(0, totalWeight);
11
+  let cumulWeight = 0;
12
+  for (let i = 0; i < weights.length; i += 1) {
13
+    cumulWeight += weights[i];
14
+    if (rand < cumulWeight) {
15
+      return choices[i];
16
+    }
17
+  }
18
+};
19
+
8 20
 export const randomPoint = () => (
9 21
   new PIXI.Point(randomInt(0, window.innerWidth), randomInt(0, window.innerHeight))
10 22
 );
11 23
 
24
+export const pointsEqual = (pointA: PIXI.Point, pointB: PIXI.Point): boolean => (
25
+  (pointA.x === pointB.x && pointA.y === pointB.y)
26
+);
27
+
12 28
 export const distance = (pointA: PIXI.Point, pointB: PIXI.Point): number => {
13 29
   const distX = pointA.x - pointB.x;
14 30
   const distY = pointA.y - pointB.y;