diff --git a/src/Line.ts b/src/Line.ts index 48ccd3b..b37a548 100644 --- a/src/Line.ts +++ b/src/Line.ts @@ -1,37 +1,20 @@ import Station from './Station'; import { distance, randomInt, randomPoint } from './utils'; -const largestStation = (stations: Station[]): Station => { - let largest: Station = null; - for (const station of stations) { - if (largest === null || station.population > largest.population) { - largest = station; - } - } - return largest; -}; - -const stationsWithinRadius = (stations: Station[], point: PIXI.Point, - radius: number): Station[] => ( - stations.filter(station => distance(point, station.location) <= radius) -); - -const closestStations = (stations: Station[], point: PIXI.Point, num: number): Station[] => { - // bleh, i'm done - return stations; -}; - export default class Line { public stations: Station[]; constructor(stations: Station[], numStations: number) { this.stations = []; let stationsLeft = stations; - let largest = largestStation(stationsLeft); + let largest = Station.largestStation(stationsLeft); stationsLeft = stationsLeft.filter(s => s !== largest); this.stations.push(largest); + while (this.stations.length < numStations) { - largest = largestStation(stationsWithinRadius(stationsLeft, largest.location, 500)); + largest = Station.largestStation( + Station.stationsWithinRadius(stationsLeft, largest.location, 500), + ); if (largest === null) { break; } diff --git a/src/Station.ts b/src/Station.ts index 3cdf723..920b794 100644 --- a/src/Station.ts +++ b/src/Station.ts @@ -1,4 +1,28 @@ +import { distance } from './utils'; + export default class Station { + // Utility methods for working with arrays of Stations + public static largestStation(stations: Station[]): Station { + let largest: Station = null; + for (const station of stations) { + if (largest === null || station.population > largest.population) { + largest = station; + } + } + return largest; + } + + public static stationsWithinRadius(stations: Station[], point: PIXI.Point, + radius: number): Station[] { + return stations.filter(station => distance(point, station.location) <= radius); + } + + public static closestStation(stations: Station[], point: PIXI.Point, num: number): Station { + return stations.reduce( + (prev, curr) => distance(point, prev.location) > distance(point, curr.location) ? prev : curr, + ); + } + public location: PIXI.Point; public population: number; public connections: Station[]; diff --git a/src/transport.ts b/src/transport.ts index afe8149..fdaf340 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -2,10 +2,13 @@ import * as PIXI from 'pixi.js'; import Line from './Line'; import Station from './Station'; import Train from './Train'; -import { distance, randomInt, randomPoint } from './utils'; +import { distance, pointsEqual, randomInt, randomPoint, weightedRandom } from './utils'; import './style.css'; +const maxSpeed = 10.0; +const acceleration = 0.25; + const isPointDistant = (point: PIXI.Point, stations: Station[], minDistance: number): boolean => { for (const station of stations) { if (distance(point, station.location) < minDistance) { @@ -45,12 +48,53 @@ const initTrains = (numTrains: number, stations: Station[]): Train[] => { const trains = []; for (let i = 0; i < numTrains; i += 1) { const originStation = stations[Math.floor(Math.random() * stations.length)]; - const destStation = stations[Math.floor(Math.random() * stations.length)]; - trains.push(new Train(originStation.location, 0, 0, originStation, destStation)); + // const destStation = stations[Math.floor(Math.random() * stations.length)]; + trains.push(new Train(originStation.location, 0, 0, originStation, undefined)); } return trains; }; +const moveTrains = (trains: Train[], stations: Station[]) => { + for (const train of trains) { + // choose a destination randomly with a bias towards larger stations + if (train.destination === undefined) { + const closeStations = Station.stationsWithinRadius(stations, train.location, 500); + const closeStationWeights = closeStations.map(station => station.population); + train.destination = weightedRandom(closeStations, closeStationWeights); + } + + // train reached destination, stop moving. + if (pointsEqual(train.location, train.destination.location)) { + train.speed = 0; + continue; + } + + const journeyLeft = distance(train.location, train.destination.location); + // speeding up + if (train.speed < maxSpeed) { + train.speed += acceleration; + } + + // slowing down + if ((train.speed / acceleration) >= (journeyLeft / train.speed)) { + train.speed -= acceleration; + } + + // advance train + const progress = train.speed / journeyLeft; + train.location = new PIXI.Point( + train.location.x + (Math.abs(train.location.x - train.destination.location.x) * progress), + train.location.y + (Math.abs(train.location.y - train.destination.location.y) * progress), + ); + } +}; + +const drawTrains = (trains: Train[], graphics: PIXI.Graphics) => { + for (const train of trains) { + graphics.drawCircle(train.location.x, train.location.y, 2); + } +}; + const drawLine = (line: Line, graphics: PIXI.Graphics) => { const start = line.stations[0].location; graphics.moveTo(start.x, start.y); @@ -76,21 +120,22 @@ const run = () => { // make these const let stations = initStations(30); let trains = initTrains(15, stations); - let line = new Line(stations, 10); + // let line = new Line(stations, 10); stations = initStations(30); trains = initTrains(15, stations); - line = new Line(stations, 10); ticker.stop(); ticker.add((deltaTime) => { + moveTrains(trains, stations); graphics.clear(); fpsText.text = `${Math.round(ticker.FPS)}`; graphics.lineStyle(1, 0xaeaeae, 1); drawStations(stations, graphics); - drawLine(line, graphics); + drawTrains(trains, graphics); + // drawLine(line, graphics); }); ticker.start(); diff --git a/src/utils.ts b/src/utils.ts index 94d1723..ebbfbe6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,10 +5,26 @@ export const randomInt = (min: number, max: number): number => ( Math.floor(Math.random() * (max - (min + 1))) + min ); +export const weightedRandom = (choices: any[], weights: number[]): any => { + const totalWeight = weights.reduce((a, b) => a + b, 0); + const rand = randomInt(0, totalWeight); + let cumulWeight = 0; + for (let i = 0; i < weights.length; i += 1) { + cumulWeight += weights[i]; + if (rand < cumulWeight) { + return choices[i]; + } + } +}; + export const randomPoint = () => ( new PIXI.Point(randomInt(0, window.innerWidth), randomInt(0, window.innerHeight)) ); +export const pointsEqual = (pointA: PIXI.Point, pointB: PIXI.Point): boolean => ( + (pointA.x === pointB.x && pointA.y === pointB.y) +); + export const distance = (pointA: PIXI.Point, pointB: PIXI.Point): number => { const distX = pointA.x - pointB.x; const distY = pointA.y - pointB.y;