Allow railroad loops (#1274)

## Description:

This change enables loop formation in the station network, allowing for
enhanced connectivity. However, it can sometimes result in clustered
areas as a trade-off. To mitigate excessive clustering, loops are only
permitted when there are at least 5 intermediary stations between the
source and destination stations.


![image](https://github.com/user-attachments/assets/720fa4f9-a634-49e8-8027-fe4e244d5ae5)

Also a few fixes for `.dev` players.

## 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
This commit is contained in:
DevelopingTom
2025-06-27 22:00:11 +02:00
committed by GitHub
parent f905e29ec6
commit c1bf01d3ba
4 changed files with 54 additions and 6 deletions
+4 -1
View File
@@ -246,7 +246,10 @@ export class UnitInfoModal extends LitElement implements Layer {
class="upgrade-button"
title="${translateText("unit_info_modal.create_station")}"
style="width: 100px; height: 32px;
display: ${this.unit.hasTrainStation() ? "none" : "block"};"
display: ${this.unit.hasTrainStation() ||
!this.game.unitInfo(this.unit.type()).canBuildTrainStation
? "none"
: "block"};"
>
${translateText("unit_info_modal.create_station")}
</button>
+4 -1
View File
@@ -302,7 +302,7 @@ export class DefaultConfig implements Config {
return Math.min(50, Math.round(10 * Math.pow(numberOfPorts, 0.6)));
}
trainSpawnRate(numberOfStations: number): number {
return Math.round(50 * Math.pow(numberOfStations, 0.8));
return Math.min(1400, Math.round(60 * Math.pow(numberOfStations, 0.8)));
}
trainGold(): Gold {
return BigInt(10_000);
@@ -363,6 +363,7 @@ export class DefaultConfig implements Config {
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
upgradable: true,
canBuildTrainStation: true,
};
case UnitType.AtomBomb:
return {
@@ -452,6 +453,7 @@ export class DefaultConfig implements Config {
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
upgradable: true,
canBuildTrainStation: true,
};
case UnitType.Factory:
return {
@@ -466,6 +468,7 @@ export class DefaultConfig implements Config {
),
territoryBound: true,
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
canBuildTrainStation: true,
};
case UnitType.Construction:
return {
+1
View File
@@ -132,6 +132,7 @@ export interface UnitInfo {
damage?: number;
constructionDuration?: number;
upgradable?: boolean;
canBuildTrainStation?: boolean;
}
export enum UnitType {
+45 -4
View File
@@ -88,6 +88,8 @@ export function createRailNetwork(game: Game): RailNetwork {
}
export class RailNetworkImpl implements RailNetwork {
private maxConnectionDistance: number = 4;
constructor(
private game: Game,
private stationManager: StationManager,
@@ -142,12 +144,20 @@ export class RailNetworkImpl implements RailNetwork {
const neighborStation = this.stationManager.findStation(neighbor.unit);
if (!neighborStation) continue;
const neighborCluster = neighborStation.getCluster();
if (!neighborCluster || neighborCluster.has(station)) continue;
const distanceToStation = this.distanceFrom(
neighborStation,
station,
this.maxConnectionDistance,
);
const neighborCluster = neighborStation.getCluster();
if (neighborCluster === null) continue;
const connectionAvailable =
distanceToStation > this.maxConnectionDistance ||
distanceToStation === -1;
if (
neighbor.distSquared >
this.game.config().trainStationMinRange() ** 2
connectionAvailable &&
neighbor.distSquared > this.game.config().trainStationMinRange() ** 2
) {
if (this.connect(station, neighborStation)) {
neighborCluster.addStation(station);
@@ -196,6 +206,37 @@ export class RailNetworkImpl implements RailNetwork {
return false;
}
private distanceFrom(
start: TrainStation,
dest: TrainStation,
maxDistance: number,
): number {
if (start === dest) return 0;
const visited = new Set<TrainStation>();
const queue: Array<{ station: TrainStation; distance: number }> = [
{ station: start, distance: 0 },
];
while (queue.length > 0) {
const { station, distance } = queue.shift()!;
if (visited.has(station)) continue;
visited.add(station);
if (distance >= maxDistance) continue;
for (const neighbor of station.neighbors()) {
if (neighbor === dest) return distance + 1;
if (!visited.has(neighbor)) {
queue.push({ station: neighbor, distance: distance + 1 });
}
}
}
// If destination not found within maxDistance
return -1;
}
private computeCluster(start: TrainStation): Set<TrainStation> {
const visited = new Set<TrainStation>();
const queue = [start];