mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-25 15:04:36 +00:00
43397779fa
## Description: Add a rail network to handle train stations/railroad between structures. Changes: - `RailNetwork` is responsible for the train station graph. Use it to connect new `TrainStations` - A `RailRoad` connects two `TrainStation` - No loop possible in the rail network - Train stations handles its railroads - Added a layer to draw the railroads under the structures #### Clusters - To speed up computations, each `TrainStation` references its own cluster - A cluster is a list of `TrainStation` connected with each other, created by the `RailNetwork` when connecting the station - Train stations spawn trains randomly depending on its current cluster size - A `TrainStation` decides randomly of the train destination by picking one from the cluster #### Production building: - Added a factory which has no gameplay impact currently. _To be discussed._ #### Train stops: - When a train reaches a factory, it's filled with a "cargo". The loaded trains has no impact currently. _To be discussed._ - When a train reaches a city, the player earn 10k gold - When a train reaches a port, it sends a new tradeship if possible - If a destination/source is destroyed, the train & railroad are deleted too https://github.com/user-attachments/assets/42375c17-9e04-4a42-98d0-708c81ffd609 https://github.com/user-attachments/assets/fbecdb53-a516-4df8-87fb-1f9a62c4efa0 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom --------- Co-authored-by: Scott Anderson <scottanderson@users.noreply.github.com>
159 lines
4.3 KiB
TypeScript
159 lines
4.3 KiB
TypeScript
export class PseudoRandom {
|
||
// Internal state (two 32-bit integers)
|
||
private state0: number;
|
||
private state1: number;
|
||
|
||
// Keep these variables to maintain the exact same interface
|
||
private m: number = 0x80000000; // 2**31
|
||
private a: number = 1103515245;
|
||
private c: number = 12345;
|
||
private state: number;
|
||
|
||
private static readonly POW36_8 = Math.pow(36, 8); // Pre-compute 36^8
|
||
private static readonly INV_2_32 = 1 / 4294967296; // 1 / 2^32 for float conversion
|
||
|
||
constructor(seed: number) {
|
||
// Initialize the XorShift state with seed
|
||
this.state0 = seed | 0; // Force to 32-bit integer with bitwise OR
|
||
this.state1 = 0x6e2d786c; // Fixed value as second seed (arbitrary prime)
|
||
|
||
// Ensure non-zero state
|
||
if (this.state0 === 0) this.state0 = 1;
|
||
|
||
// Also set the LCG state variable to maintain interface
|
||
this.state = seed % this.m;
|
||
if (this.state < 0) this.state += this.m;
|
||
|
||
// Warm up the generator to improve initial distribution
|
||
for (let i = 0; i < 20; i++) {
|
||
this._nextIntInternal();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Internal function that implements XorShift algorithm
|
||
* @returns A 32-bit integer
|
||
*/
|
||
private _nextIntInternal(): number {
|
||
// Get current state
|
||
let s1 = this.state0;
|
||
const s0 = this.state1;
|
||
|
||
// Update state using XorShift algorithm (all operations are bitwise)
|
||
this.state0 = s0;
|
||
s1 ^= s1 << 23;
|
||
s1 ^= s1 >>> 17;
|
||
s1 ^= s0;
|
||
s1 ^= s0 >>> 26;
|
||
this.state1 = s1;
|
||
|
||
// Generate output (force 32-bit integer)
|
||
return (this.state0 + this.state1) | 0;
|
||
}
|
||
|
||
/**
|
||
* Optimized version that directly returns unsigned 32-bit integer
|
||
*/
|
||
private _nextUInt32(): number {
|
||
return this._nextIntInternal() >>> 0;
|
||
}
|
||
|
||
/**
|
||
* Generates the next pseudorandom number.
|
||
* @returns A number between 0 (inclusive) and 1 (exclusive).
|
||
*/
|
||
next(): number {
|
||
// Get a 32-bit integer and convert to [0,1) range
|
||
// Using >>> 0 to get unsigned interpretation (positive number)
|
||
const int = this._nextUInt32();
|
||
|
||
// Update the state variable to maintain compatibility with original interface
|
||
this.state = int % this.m;
|
||
|
||
// Convert to [0,1) range - using division for same interface
|
||
return this.state / this.m;
|
||
}
|
||
|
||
/**
|
||
* Optimized version for internal use - directly converts to [0,1) without state update
|
||
*/
|
||
private _nextFloat(): number {
|
||
return this._nextUInt32() * PseudoRandom.INV_2_32;
|
||
}
|
||
|
||
/**
|
||
* Generates a random integer between min (inclusive) and max (exclusive).
|
||
*/
|
||
nextInt(min: number, max: number): number {
|
||
// keep max exclusive, min inclusive – round down to get an int
|
||
return Math.floor(this._nextFloat() * (max - min)) + min;
|
||
}
|
||
|
||
/**
|
||
* Generates a random float between min (inclusive) and max (exclusive).
|
||
*/
|
||
nextFloat(min: number, max: number): number {
|
||
return this._nextFloat() * (max - min) + min;
|
||
}
|
||
|
||
/**
|
||
* Generates a random ID (8 characters, alphanumeric).
|
||
*/
|
||
nextID(): string {
|
||
return Math.floor(this._nextFloat() * PseudoRandom.POW36_8) // 36^8 possibilities
|
||
.toString(36) // Convert to base36 (0-9 and a-z)
|
||
.padStart(8, "0"); // Ensure 8 chars by padding with zeros
|
||
}
|
||
|
||
/**
|
||
* Selects a random element from an array.
|
||
*/
|
||
randElement<T>(arr: T[]): T {
|
||
if (arr.length === 0) {
|
||
throw new Error("array must not be empty");
|
||
}
|
||
return arr[Math.floor(this._nextFloat() * arr.length)];
|
||
}
|
||
|
||
/**
|
||
* Selects a random element from a set.
|
||
*/
|
||
randFromSet<T>(set: Set<T>): T {
|
||
const size = set.size;
|
||
if (size === 0) {
|
||
throw new Error("set must not be empty");
|
||
}
|
||
|
||
const index = this.nextInt(0, size);
|
||
let i = 0;
|
||
for (const item of set) {
|
||
if (i === index) {
|
||
return item;
|
||
}
|
||
i++;
|
||
}
|
||
|
||
// This should never happen
|
||
throw new Error("Unexpected error selecting element from set");
|
||
}
|
||
|
||
/**
|
||
* Returns true with probability 1/odds.
|
||
*/
|
||
chance(odds: number): boolean {
|
||
return Math.floor(this._nextFloat() * odds) === 0;
|
||
}
|
||
|
||
/**
|
||
* Returns a shuffled copy of the array using Fisher-Yates algorithm.
|
||
*/
|
||
shuffleArray<T>(array: T[]): T[] {
|
||
const result = [...array];
|
||
for (let i = result.length - 1; i >= 0; i--) {
|
||
const j = Math.floor(this._nextFloat() * (i + 1));
|
||
[result[i], result[j]] = [result[j], result[i]];
|
||
}
|
||
return result;
|
||
}
|
||
}
|