powergrid-engine 1.11.0 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@ export interface AvailableMoves {
9
9
  resource: ResourceType;
10
10
  price: number;
11
11
  side?: 'north' | 'south';
12
+ fromStorage?: boolean;
12
13
  }[];
13
14
  [MoveName.Build]?: {
14
15
  name: string;
@@ -66,6 +66,15 @@ function availableMoves(G, player) {
66
66
  canBid = canBid.filter((p) => p.type != gamestate_1.PowerPlantType.Uranium);
67
67
  }
68
68
  }
69
+ // No nuclear plants for Republic of Ireland (Green region) on UK&I.
70
+ // The restriction lifts as soon as the player has any non-Green city
71
+ // (Scotland/Wales/England/Northern Ireland = Brown/Yellow/Red/Pink/Orange).
72
+ if (G.map.name == 'UK & Ireland') {
73
+ const playerCities = player.cities.map((c) => G.map.cities.find((c_) => c_.name == c.name));
74
+ if (playerCities.every((c) => c.region == 'green')) {
75
+ canBid = canBid.filter((p) => p.type != gamestate_1.PowerPlantType.Uranium);
76
+ }
77
+ }
69
78
  if (canBid.length > 0) {
70
79
  moves[move_1.MoveName.ChoosePowerPlant] = canBid.map((p) => p.number);
71
80
  }
@@ -107,6 +116,14 @@ function availableMoves(G, player) {
107
116
  moves[move_1.MoveName.Bid] = undefined;
108
117
  }
109
118
  }
119
+ // No nuclear plants for Republic of Ireland (Green region) on UK&I.
120
+ if (G.map.name == 'UK & Ireland') {
121
+ const playerCities = player.cities.map((c) => G.map.cities.find((c_) => c_.name == c.name));
122
+ if (playerCities.every((c) => c.region == 'green') &&
123
+ G.chosenPowerPlant.type == gamestate_1.PowerPlantType.Uranium) {
124
+ moves[move_1.MoveName.Bid] = undefined;
125
+ }
126
+ }
110
127
  if (G.options.fastBid) {
111
128
  if (player.id != G.auctioningPlayer) {
112
129
  moves[move_1.MoveName.Pass] = [true];
@@ -159,6 +176,17 @@ function availableMoves(G, player) {
159
176
  }
160
177
  }
161
178
  }
179
+ // South Africa: $8 flat coal from the storage pool below the market.
180
+ // Always available alongside the regular market option (not gated on
181
+ // market being empty), as long as there are cubes in storage.
182
+ if (allowSouth && G.coalStorage !== undefined && G.coalStorage > 0) {
183
+ const hybridCapacityUsed = player.hybridCapacity > 0 ? Math.max(0, player.oilLeft - player.oilCapacity) : 0;
184
+ if (player.money >= 8 &&
185
+ player.coalCapacity + player.hybridCapacity > hybridCapacityUsed + player.coalLeft &&
186
+ 8 <= maxPriceAvailable) {
187
+ toBuy.push({ resource: gamestate_1.ResourceType.Coal, fromStorage: true });
188
+ }
189
+ }
162
190
  if (allowNorth && G.coalMarketNorth > 0) {
163
191
  const hybridCapacityUsed = player.hybridCapacity > 0 ? Math.max(0, player.oilLeft - player.oilCapacity) : 0;
164
192
  const coalPrices = G.coalPricesNorth;
@@ -256,6 +284,44 @@ function availableMoves(G, player) {
256
284
  }
257
285
  return;
258
286
  }
287
+ // UK & Ireland: starting a network on an island where the player has
288
+ // no city yet pays the first-house base + crossIslandSurcharge. There
289
+ // is no sea edge so dijkstra reports the target city as unreachable
290
+ // (price=9999); we override here. The first build ever (player.cities
291
+ // empty) goes through the normal first-build path and pays no surcharge.
292
+ if (cityData.island && G.map.crossIslandSurcharge !== undefined && player.cities.length > 0) {
293
+ const playerIslands = new Set(player.cities
294
+ .map((c) => { var _a; return (_a = G.map.cities.find((mc) => mc.name == c.name)) === null || _a === void 0 ? void 0 : _a.island; })
295
+ .filter((i) => !!i));
296
+ if (!playerIslands.has(cityData.island)) {
297
+ city.price = 10 + othersCount * 5 + G.map.crossIslandSurcharge;
298
+ if (othersCount == G.step) {
299
+ city.price = 9999;
300
+ }
301
+ if (player.cities.find((c) => c.name == city.name)) {
302
+ city.price = 9999;
303
+ }
304
+ return;
305
+ }
306
+ }
307
+ // South Africa's cross-border foreign-country spaces: cap at 1 occupant
308
+ // ever, and the dijkstra path cost (30 via the cross-border edge) is the
309
+ // complete cost — no 10+position*5 house base is added. Players cannot
310
+ // start in one of these (you have to build INTO South Africa first).
311
+ if (cityData.singleOccupancy) {
312
+ if (player.cities.length == 0) {
313
+ city.price = 9999;
314
+ return;
315
+ }
316
+ if (othersCount >= 1) {
317
+ city.price = 9999;
318
+ return;
319
+ }
320
+ if (player.cities.find((c) => c.name == city.name)) {
321
+ city.price = 9999;
322
+ }
323
+ return;
324
+ }
259
325
  city.price += 10 + othersCount * 5;
260
326
  if (othersCount == G.step) {
261
327
  city.price = 9999;
@@ -18,6 +18,7 @@ const utils_1 = require("./utils");
18
18
  exports.playerColors = ['limegreen', 'mediumorchid', 'red', 'dodgerblue', 'yellow', 'brown'];
19
19
  const citiesToStep2 = [10, 7, 7, 7, 6];
20
20
  const citiesToStep2BadenWurttemberg = [9, 6, 6, 6, 5];
21
+ const citiesToStep2UKIreland = [7, 7, 7, 7, 6];
21
22
  const citiesToEndGame = [21, 17, 17, 15, 14];
22
23
  const cityIncome = [10, 22, 33, 44, 54, 64, 73, 82, 90, 98, 105, 112, 118, 124, 129, 134, 138, 142, 145, 148, 150, 150];
23
24
  const regionsInPlay = [3, 3, 4, 5, 5];
@@ -73,7 +74,11 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
73
74
  var _a, _b, _c, _d, _e, _f, _g;
74
75
  seed = seed !== null && seed !== void 0 ? seed : Math.random().toString();
75
76
  const rng = seedrandom_1.default(seed);
76
- const chosenMap = lodash_1.cloneDeep(variant == 'original' ? maps_1.maps.find((m) => m.name == map) : maps_1.mapsRecharged.find((m) => m.name == map));
77
+ const chosenMapRaw = variant == 'original' ? maps_1.maps.find((m) => m.name == map) : maps_1.mapsRecharged.find((m) => m.name == map);
78
+ if (!chosenMapRaw) {
79
+ throw new Error(`Map "${map}" not found for variant "${variant}"`);
80
+ }
81
+ const chosenMap = lodash_1.cloneDeep(chosenMapRaw);
77
82
  let actualMarket;
78
83
  let futureMarket;
79
84
  let powerPlantsDeck;
@@ -220,7 +225,14 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
220
225
  while (playRegions.size != Math.min(regionsInPlay[p], regions.length)) {
221
226
  const region = regions[Math.floor(rng() * regions.length)];
222
227
  if (playRegions.size == 0 ||
223
- regionConnections[regions.indexOf(region)].some((con) => playRegions.has(con))) {
228
+ regionConnections[regions.indexOf(region)].some((con) => playRegions.has(con)) ||
229
+ // UK & Ireland: regions on the two islands have no edges between
230
+ // them (no sea connection). Skipping the connectivity check lets
231
+ // the random selection span both islands; the cross-island
232
+ // surcharge handles the disconnect at build time. Without this,
233
+ // requiring 5-of-6 regions for 5p would loop forever (GB has 4
234
+ // regions, IE has 2).
235
+ chosenMap.name === 'UK & Ireland') {
224
236
  playRegions.add(region);
225
237
  // Avoid italy Red Green Blue
226
238
  if (chosenMap.name === 'Italy') {
@@ -280,6 +292,7 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
280
292
  const coalPricesNorth = chosenMap.coalPricesNorth ? lodash_1.cloneDeep(chosenMap.coalPricesNorth) : undefined;
281
293
  const oilPricesNorth = chosenMap.oilPricesNorth ? lodash_1.cloneDeep(chosenMap.oilPricesNorth) : undefined;
282
294
  const garbagePricesNorth = chosenMap.garbagePricesNorth ? lodash_1.cloneDeep(chosenMap.garbagePricesNorth) : undefined;
295
+ const isSouthAfrica = (forceMap || finalMap).name == 'South Africa';
283
296
  const G = {
284
297
  map: forceMap || finalMap,
285
298
  players,
@@ -290,6 +303,9 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
290
303
  oilSupply,
291
304
  garbageSupply,
292
305
  uraniumSupply,
306
+ // South Africa: separate coal pool below the market. Starts empty;
307
+ // used coal returns here; refill draws from here first.
308
+ coalStorage: isSouthAfrica ? 0 : undefined,
293
309
  coalResupply,
294
310
  oilResupply,
295
311
  garbageResupply,
@@ -329,7 +345,9 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
329
345
  auctionSkips: 0,
330
346
  citiesToStep2: (forceMap || finalMap).name == 'Baden-Württemberg'
331
347
  ? citiesToStep2BadenWurttemberg[numPlayers - 2]
332
- : citiesToStep2[numPlayers - 2],
348
+ : (forceMap || finalMap).name == 'UK & Ireland'
349
+ ? citiesToStep2UKIreland[numPlayers - 2]
350
+ : citiesToStep2[numPlayers - 2],
333
351
  citiesToEndGame: citiesToEndGame[numPlayers - 2],
334
352
  resourceResupply: [
335
353
  `[${coalResupply[p][0]}, ${oilResupply[p][0]}, ${garbageResupply[p][0]}, ${uraniumResupply[p][0]}]`,
@@ -648,7 +666,18 @@ function move(G, move, playerNumber, isUndo = false) {
648
666
  G.powerPlantsDeck.unshift(getPowerPlant(18));
649
667
  }
650
668
  }
651
- addPowerPlant(G);
669
+ if (G.map.name == 'Europe') {
670
+ // Europe: do NOT draw a replacement from the deck.
671
+ // The future market shrinks from 5 to 4; reorganize
672
+ // the remaining 8 plants so actual stays at 4.
673
+ const market = [...G.actualMarket, ...G.futureMarket];
674
+ market.sort((a, b) => a.number - b.number);
675
+ G.actualMarket = market.slice(0, 4);
676
+ G.futureMarket = market.slice(4);
677
+ }
678
+ else {
679
+ addPowerPlant(G);
680
+ }
652
681
  }
653
682
  }
654
683
  if (maxCities >= G.citiesToEndGame) {
@@ -742,9 +771,23 @@ function move(G, move, playerNumber, isUndo = false) {
742
771
  G.garbageMarketNorth += garbageResupplyNorthValue;
743
772
  G.garbageSupply -= garbageResupplyNorthValue;
744
773
  }
745
- const coalResupplyValue = Math.min(G.coalSupply, G.coalResupply[G.players.length - 2][G.step - 1], coalCapSouth);
746
- G.coalMarket += coalResupplyValue;
747
- G.coalSupply -= coalResupplyValue;
774
+ // South Africa pulls from coalStorage first, then coalSupply.
775
+ // Other maps just pull from coalSupply.
776
+ let coalResupplyValue;
777
+ if (G.coalStorage !== undefined) {
778
+ const wantCoal = Math.min(G.coalResupply[G.players.length - 2][G.step - 1], coalCapSouth);
779
+ const fromStorage = Math.min(G.coalStorage, wantCoal);
780
+ const fromSupply = Math.min(G.coalSupply, wantCoal - fromStorage);
781
+ coalResupplyValue = fromStorage + fromSupply;
782
+ G.coalMarket += coalResupplyValue;
783
+ G.coalStorage -= fromStorage;
784
+ G.coalSupply -= fromSupply;
785
+ }
786
+ else {
787
+ coalResupplyValue = Math.min(G.coalSupply, G.coalResupply[G.players.length - 2][G.step - 1], coalCapSouth);
788
+ G.coalMarket += coalResupplyValue;
789
+ G.coalSupply -= coalResupplyValue;
790
+ }
748
791
  let oilResupplyValue;
749
792
  if (G.map.name == 'Middle East') {
750
793
  if (G.oilMarket == 0) {
@@ -1077,6 +1120,12 @@ function move(G, move, playerNumber, isUndo = false) {
1077
1120
  player.coalLeft++;
1078
1121
  G.coalMarketNorth--;
1079
1122
  }
1123
+ else if (move.data.fromStorage) {
1124
+ // South Africa: $8 flat from the storage pool below the market.
1125
+ price = 8;
1126
+ player.coalLeft++;
1127
+ G.coalStorage--;
1128
+ }
1080
1129
  else if (G.coalMarket == 0) {
1081
1130
  price = 8;
1082
1131
  player.coalLeft++;
@@ -1156,8 +1205,15 @@ function move(G, move, playerNumber, isUndo = false) {
1156
1205
  player.cities.push({ name: move.data.name, position });
1157
1206
  player.money -= move.data.price;
1158
1207
  if (G.options.trackTotalSpent) {
1159
- player.totalSpentCities += 10 + position * 5;
1160
- player.totalSpentConnections += move.data.price - (10 + position * 5);
1208
+ const cityData = G.map.cities.find((c) => c.name == move.data.name);
1209
+ if (cityData.singleOccupancy) {
1210
+ // SA cross-border: no house base — full price is "connection".
1211
+ player.totalSpentConnections += move.data.price;
1212
+ }
1213
+ else {
1214
+ player.totalSpentCities += 10 + position * 5;
1215
+ player.totalSpentConnections += move.data.price - (10 + position * 5);
1216
+ }
1161
1217
  }
1162
1218
  G.log.push({
1163
1219
  type: 'move',
@@ -1188,7 +1244,13 @@ function move(G, move, playerNumber, isUndo = false) {
1188
1244
  switch (resourceType) {
1189
1245
  case gamestate_1.ResourceType.Coal:
1190
1246
  player.coalLeft--;
1191
- G.coalSupply++;
1247
+ // SA: used coal returns to the separate storage pool below the market.
1248
+ if (G.coalStorage !== undefined) {
1249
+ G.coalStorage++;
1250
+ }
1251
+ else {
1252
+ G.coalSupply++;
1253
+ }
1192
1254
  break;
1193
1255
  case gamestate_1.ResourceType.Oil:
1194
1256
  player.oilLeft--;
@@ -1242,6 +1304,11 @@ function move(G, move, playerNumber, isUndo = false) {
1242
1304
  const coalPrices = G.coalPricesNorth;
1243
1305
  price = coalPrices[coalPrices.length - G.coalMarketNorth];
1244
1306
  }
1307
+ else if (lastMove.data.fromStorage) {
1308
+ price = 8;
1309
+ player.coalLeft--;
1310
+ G.coalStorage++;
1311
+ }
1245
1312
  else if (lastMove.fromSupply) {
1246
1313
  price = 8;
1247
1314
  player.coalLeft--;
@@ -2,7 +2,7 @@ import { AvailableMoves } from './available-moves';
2
2
  import { LogItem } from './log';
3
3
  import { GameMap } from './maps';
4
4
  import { Move } from './move';
5
- export declare type MapName = 'USA' | 'Germany' | 'Brazil' | 'Spain & Portugal' | 'France' | 'Italy' | 'Quebec' | 'Middle East' | 'India' | 'China' | 'Benelux' | 'Russia' | 'Central Europe' | 'Baden-Württemberg' | 'Northern Europe' | 'Korea';
5
+ export declare type MapName = 'USA' | 'Germany' | 'Brazil' | 'Spain & Portugal' | 'France' | 'Italy' | 'Quebec' | 'Middle East' | 'India' | 'China' | 'Benelux' | 'Russia' | 'Central Europe' | 'Baden-Württemberg' | 'Northern Europe' | 'Korea' | 'Europe' | 'North America' | 'South Africa' | 'UK & Ireland';
6
6
  export declare type Variant = 'original' | 'recharged';
7
7
  export interface GameOptions {
8
8
  fastBid?: boolean;
@@ -92,6 +92,7 @@ export interface GameState {
92
92
  oilSupply: number;
93
93
  garbageSupply: number;
94
94
  uraniumSupply: number;
95
+ coalStorage?: number;
95
96
  coalMarket: number;
96
97
  oilMarket: number;
97
98
  garbageMarket: number;
@@ -0,0 +1,62 @@
1
+ import { GameMap } from './../maps';
2
+ export declare enum Regions {
3
+ Pink = "pink",
4
+ Red = "red",
5
+ Brown = "brown",
6
+ Yellow = "yellow",
7
+ Orange = "orange",
8
+ Blue = "blue",
9
+ Green = "green"
10
+ }
11
+ export declare enum Cities {
12
+ Glasgow = "Glasgow",
13
+ Dublin = "Dublin",
14
+ Birmingham = "Birmingham",
15
+ London = "London",
16
+ Randstad = "Randstad",
17
+ Vlaanderen = "Vlaanderen",
18
+ RheinRuhr = "Rhein-Ruhr",
19
+ Paris = "Paris",
20
+ Bordeaux = "Bordeaux",
21
+ Lyon = "Lyon",
22
+ Marseille = "Marseille",
23
+ Lisboa = "Lisboa",
24
+ Madrid = "Madrid",
25
+ Barcelona = "Barcelona",
26
+ Bremen = "Bremen",
27
+ Berlin = "Berlin",
28
+ RheinMain = "Rhein-Main",
29
+ Stuttgart = "Stuttgart",
30
+ Praha = "Praha",
31
+ Katowice = "Katowice",
32
+ München = "M\u00FCnchen",
33
+ Zürich = "Z\u00FCrich",
34
+ Milano = "Milano",
35
+ Roma = "Roma",
36
+ Napoli = "Napoli",
37
+ Wien = "Wien",
38
+ Budapest = "Budapest",
39
+ Zagreb = "Zagreb",
40
+ Oslo = "Oslo",
41
+ Stockholm = "Stockholm",
42
+ Helsinki = "Helsinki",
43
+ Tallinn = "Tallinn",
44
+ Kobenhavn = "K\u00F8benhavn",
45
+ Riga = "Riga",
46
+ StPetersburg = "St. Petersburg",
47
+ Minsk = "Minsk",
48
+ Moskwa = "Moskwa",
49
+ Warszawa = "Warszawa",
50
+ Kyjiv = "Kyjiv",
51
+ Kharkiv = "Kharkiv",
52
+ Odessa = "Odessa",
53
+ Bucuresti = "Bucure\u015Fti",
54
+ Beograd = "Beograd",
55
+ Sofia = "Sofia",
56
+ Tirana = "Tirana",
57
+ Athina = "Athina",
58
+ Istanbul = "Istanbul",
59
+ Izmir = "Izmir",
60
+ Ankara = "Ankara"
61
+ }
62
+ export declare const map: GameMap;