powergrid-engine 1.11.0 → 1.12.0

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];
@@ -220,7 +221,14 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
220
221
  while (playRegions.size != Math.min(regionsInPlay[p], regions.length)) {
221
222
  const region = regions[Math.floor(rng() * regions.length)];
222
223
  if (playRegions.size == 0 ||
223
- regionConnections[regions.indexOf(region)].some((con) => playRegions.has(con))) {
224
+ regionConnections[regions.indexOf(region)].some((con) => playRegions.has(con)) ||
225
+ // UK & Ireland: regions on the two islands have no edges between
226
+ // them (no sea connection). Skipping the connectivity check lets
227
+ // the random selection span both islands; the cross-island
228
+ // surcharge handles the disconnect at build time. Without this,
229
+ // requiring 5-of-6 regions for 5p would loop forever (GB has 4
230
+ // regions, IE has 2).
231
+ chosenMap.name === 'UK & Ireland') {
224
232
  playRegions.add(region);
225
233
  // Avoid italy Red Green Blue
226
234
  if (chosenMap.name === 'Italy') {
@@ -280,6 +288,7 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
280
288
  const coalPricesNorth = chosenMap.coalPricesNorth ? lodash_1.cloneDeep(chosenMap.coalPricesNorth) : undefined;
281
289
  const oilPricesNorth = chosenMap.oilPricesNorth ? lodash_1.cloneDeep(chosenMap.oilPricesNorth) : undefined;
282
290
  const garbagePricesNorth = chosenMap.garbagePricesNorth ? lodash_1.cloneDeep(chosenMap.garbagePricesNorth) : undefined;
291
+ const isSouthAfrica = (forceMap || finalMap).name == 'South Africa';
283
292
  const G = {
284
293
  map: forceMap || finalMap,
285
294
  players,
@@ -290,6 +299,9 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
290
299
  oilSupply,
291
300
  garbageSupply,
292
301
  uraniumSupply,
302
+ // South Africa: separate coal pool below the market. Starts empty;
303
+ // used coal returns here; refill draws from here first.
304
+ coalStorage: isSouthAfrica ? 0 : undefined,
293
305
  coalResupply,
294
306
  oilResupply,
295
307
  garbageResupply,
@@ -329,7 +341,9 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
329
341
  auctionSkips: 0,
330
342
  citiesToStep2: (forceMap || finalMap).name == 'Baden-Württemberg'
331
343
  ? citiesToStep2BadenWurttemberg[numPlayers - 2]
332
- : citiesToStep2[numPlayers - 2],
344
+ : (forceMap || finalMap).name == 'UK & Ireland'
345
+ ? citiesToStep2UKIreland[numPlayers - 2]
346
+ : citiesToStep2[numPlayers - 2],
333
347
  citiesToEndGame: citiesToEndGame[numPlayers - 2],
334
348
  resourceResupply: [
335
349
  `[${coalResupply[p][0]}, ${oilResupply[p][0]}, ${garbageResupply[p][0]}, ${uraniumResupply[p][0]}]`,
@@ -648,7 +662,18 @@ function move(G, move, playerNumber, isUndo = false) {
648
662
  G.powerPlantsDeck.unshift(getPowerPlant(18));
649
663
  }
650
664
  }
651
- addPowerPlant(G);
665
+ if (G.map.name == 'Europe') {
666
+ // Europe: do NOT draw a replacement from the deck.
667
+ // The future market shrinks from 5 to 4; reorganize
668
+ // the remaining 8 plants so actual stays at 4.
669
+ const market = [...G.actualMarket, ...G.futureMarket];
670
+ market.sort((a, b) => a.number - b.number);
671
+ G.actualMarket = market.slice(0, 4);
672
+ G.futureMarket = market.slice(4);
673
+ }
674
+ else {
675
+ addPowerPlant(G);
676
+ }
652
677
  }
653
678
  }
654
679
  if (maxCities >= G.citiesToEndGame) {
@@ -742,9 +767,23 @@ function move(G, move, playerNumber, isUndo = false) {
742
767
  G.garbageMarketNorth += garbageResupplyNorthValue;
743
768
  G.garbageSupply -= garbageResupplyNorthValue;
744
769
  }
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;
770
+ // South Africa pulls from coalStorage first, then coalSupply.
771
+ // Other maps just pull from coalSupply.
772
+ let coalResupplyValue;
773
+ if (G.coalStorage !== undefined) {
774
+ const wantCoal = Math.min(G.coalResupply[G.players.length - 2][G.step - 1], coalCapSouth);
775
+ const fromStorage = Math.min(G.coalStorage, wantCoal);
776
+ const fromSupply = Math.min(G.coalSupply, wantCoal - fromStorage);
777
+ coalResupplyValue = fromStorage + fromSupply;
778
+ G.coalMarket += coalResupplyValue;
779
+ G.coalStorage -= fromStorage;
780
+ G.coalSupply -= fromSupply;
781
+ }
782
+ else {
783
+ coalResupplyValue = Math.min(G.coalSupply, G.coalResupply[G.players.length - 2][G.step - 1], coalCapSouth);
784
+ G.coalMarket += coalResupplyValue;
785
+ G.coalSupply -= coalResupplyValue;
786
+ }
748
787
  let oilResupplyValue;
749
788
  if (G.map.name == 'Middle East') {
750
789
  if (G.oilMarket == 0) {
@@ -1077,6 +1116,12 @@ function move(G, move, playerNumber, isUndo = false) {
1077
1116
  player.coalLeft++;
1078
1117
  G.coalMarketNorth--;
1079
1118
  }
1119
+ else if (move.data.fromStorage) {
1120
+ // South Africa: $8 flat from the storage pool below the market.
1121
+ price = 8;
1122
+ player.coalLeft++;
1123
+ G.coalStorage--;
1124
+ }
1080
1125
  else if (G.coalMarket == 0) {
1081
1126
  price = 8;
1082
1127
  player.coalLeft++;
@@ -1156,8 +1201,15 @@ function move(G, move, playerNumber, isUndo = false) {
1156
1201
  player.cities.push({ name: move.data.name, position });
1157
1202
  player.money -= move.data.price;
1158
1203
  if (G.options.trackTotalSpent) {
1159
- player.totalSpentCities += 10 + position * 5;
1160
- player.totalSpentConnections += move.data.price - (10 + position * 5);
1204
+ const cityData = G.map.cities.find((c) => c.name == move.data.name);
1205
+ if (cityData.singleOccupancy) {
1206
+ // SA cross-border: no house base — full price is "connection".
1207
+ player.totalSpentConnections += move.data.price;
1208
+ }
1209
+ else {
1210
+ player.totalSpentCities += 10 + position * 5;
1211
+ player.totalSpentConnections += move.data.price - (10 + position * 5);
1212
+ }
1161
1213
  }
1162
1214
  G.log.push({
1163
1215
  type: 'move',
@@ -1188,7 +1240,13 @@ function move(G, move, playerNumber, isUndo = false) {
1188
1240
  switch (resourceType) {
1189
1241
  case gamestate_1.ResourceType.Coal:
1190
1242
  player.coalLeft--;
1191
- G.coalSupply++;
1243
+ // SA: used coal returns to the separate storage pool below the market.
1244
+ if (G.coalStorage !== undefined) {
1245
+ G.coalStorage++;
1246
+ }
1247
+ else {
1248
+ G.coalSupply++;
1249
+ }
1192
1250
  break;
1193
1251
  case gamestate_1.ResourceType.Oil:
1194
1252
  player.oilLeft--;
@@ -1242,6 +1300,11 @@ function move(G, move, playerNumber, isUndo = false) {
1242
1300
  const coalPrices = G.coalPricesNorth;
1243
1301
  price = coalPrices[coalPrices.length - G.coalMarketNorth];
1244
1302
  }
1303
+ else if (lastMove.data.fromStorage) {
1304
+ price = 8;
1305
+ player.coalLeft--;
1306
+ G.coalStorage++;
1307
+ }
1245
1308
  else if (lastMove.fromSupply) {
1246
1309
  price = 8;
1247
1310
  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;