powergrid-engine 1.10.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.
@@ -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];
@@ -70,7 +71,7 @@ function defaultSetupDeck(numPlayers, variant, rng, useNewRechargedSetup) {
70
71
  }
71
72
  exports.defaultSetupDeck = defaultSetupDeck;
72
73
  function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original', showMoney = false, useNewRechargedSetup = true, trackTotalSpent = true, randomizeMap = false, }, seed, forceDeck, forceMap) {
73
- var _a, _b, _c, _d;
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
77
  const chosenMap = lodash_1.cloneDeep(variant == 'original' ? maps_1.maps.find((m) => m.name == map) : maps_1.mapsRecharged.find((m) => m.name == map));
@@ -129,6 +130,15 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
129
130
  let oilResupply;
130
131
  let garbageResupply;
131
132
  let uraniumResupply;
133
+ // Korea: parallel North-side resupply tables (no uranium row).
134
+ let coalResupplyNorth;
135
+ let oilResupplyNorth;
136
+ let garbageResupplyNorth;
137
+ if (chosenMap.resupplyNorth) {
138
+ coalResupplyNorth = chosenMap.resupplyNorth[0];
139
+ oilResupplyNorth = chosenMap.resupplyNorth[1];
140
+ garbageResupplyNorth = chosenMap.resupplyNorth[2];
141
+ }
132
142
  if (chosenMap.resupply) {
133
143
  coalResupply = chosenMap.resupply[0];
134
144
  oilResupply = chosenMap.resupply[1];
@@ -211,7 +221,14 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
211
221
  while (playRegions.size != Math.min(regionsInPlay[p], regions.length)) {
212
222
  const region = regions[Math.floor(rng() * regions.length)];
213
223
  if (playRegions.size == 0 ||
214
- 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') {
215
232
  playRegions.add(region);
216
233
  // Avoid italy Red Green Blue
217
234
  if (chosenMap.name === 'Italy') {
@@ -227,6 +244,23 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
227
244
  .map((n) => chosenMap.cities.find((city) => city.name == n).region)
228
245
  .every((r) => playRegions.has(r)));
229
246
  finalMap = filteredMap;
247
+ if (chosenMap.regionalPowerPlants) {
248
+ for (const region of playRegions) {
249
+ const replacements = chosenMap.regionalPowerPlants[region];
250
+ if (replacements) {
251
+ for (const newPlant of replacements) {
252
+ const swapIn = (arr) => {
253
+ const idx = arr.findIndex((p) => p.number === newPlant.number);
254
+ if (idx !== -1)
255
+ arr[idx] = { ...newPlant };
256
+ };
257
+ swapIn(actualMarket);
258
+ swapIn(futureMarket);
259
+ swapIn(powerPlantsDeck);
260
+ }
261
+ }
262
+ }
263
+ }
230
264
  }
231
265
  const coalMarket = chosenMap.startingResources ? chosenMap.startingResources[0] : 24;
232
266
  const oilMarket = chosenMap.startingResources ? chosenMap.startingResources[1] : 18;
@@ -236,14 +270,25 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
236
270
  const totalOil = chosenMap.startingSupply ? chosenMap.startingSupply[1] : 24;
237
271
  const totalGarbage = chosenMap.startingSupply ? chosenMap.startingSupply[2] : 24;
238
272
  const totalUranium = chosenMap.startingSupply ? chosenMap.startingSupply[3] : 12;
239
- const coalSupply = totalCoal - coalMarket;
240
- const oilSupply = totalOil - oilMarket;
241
- const garbageSupply = totalGarbage - garbageMarket;
273
+ // Korea: parallel North-side market and prices. Supply is shared — see below.
274
+ const coalMarketNorth = (_a = chosenMap.startingResourcesNorth) === null || _a === void 0 ? void 0 : _a[0];
275
+ const oilMarketNorth = (_b = chosenMap.startingResourcesNorth) === null || _b === void 0 ? void 0 : _b[1];
276
+ const garbageMarketNorth = (_c = chosenMap.startingResourcesNorth) === null || _c === void 0 ? void 0 : _c[2];
277
+ // Supply pools are shared between both sides for Korea. `startingSupply`
278
+ // represents the TOTAL cubes in the game; the supply pool is whatever is
279
+ // left after both markets are filled.
280
+ const coalSupply = totalCoal - coalMarket - (coalMarketNorth !== null && coalMarketNorth !== void 0 ? coalMarketNorth : 0);
281
+ const oilSupply = totalOil - oilMarket - (oilMarketNorth !== null && oilMarketNorth !== void 0 ? oilMarketNorth : 0);
282
+ const garbageSupply = totalGarbage - garbageMarket - (garbageMarketNorth !== null && garbageMarketNorth !== void 0 ? garbageMarketNorth : 0);
242
283
  const uraniumSupply = totalUranium - uraniumMarket;
243
- const coalPrices = lodash_1.cloneDeep((_a = chosenMap.coalPrices) !== null && _a !== void 0 ? _a : prices_1.default.coal);
244
- const oilPrices = lodash_1.cloneDeep((_b = chosenMap.oilPrices) !== null && _b !== void 0 ? _b : prices_1.default.oil);
245
- const garbagePrices = lodash_1.cloneDeep((_c = chosenMap.garbagePrices) !== null && _c !== void 0 ? _c : prices_1.default.garbage);
246
- const uraniumPrices = lodash_1.cloneDeep((_d = chosenMap.uraniumPrices) !== null && _d !== void 0 ? _d : prices_1.default.uranium);
284
+ const coalPrices = lodash_1.cloneDeep((_d = chosenMap.coalPrices) !== null && _d !== void 0 ? _d : prices_1.default.coal);
285
+ const oilPrices = lodash_1.cloneDeep((_e = chosenMap.oilPrices) !== null && _e !== void 0 ? _e : prices_1.default.oil);
286
+ const garbagePrices = lodash_1.cloneDeep((_f = chosenMap.garbagePrices) !== null && _f !== void 0 ? _f : prices_1.default.garbage);
287
+ const uraniumPrices = lodash_1.cloneDeep((_g = chosenMap.uraniumPrices) !== null && _g !== void 0 ? _g : prices_1.default.uranium);
288
+ const coalPricesNorth = chosenMap.coalPricesNorth ? lodash_1.cloneDeep(chosenMap.coalPricesNorth) : undefined;
289
+ const oilPricesNorth = chosenMap.oilPricesNorth ? lodash_1.cloneDeep(chosenMap.oilPricesNorth) : undefined;
290
+ const garbagePricesNorth = chosenMap.garbagePricesNorth ? lodash_1.cloneDeep(chosenMap.garbagePricesNorth) : undefined;
291
+ const isSouthAfrica = (forceMap || finalMap).name == 'South Africa';
247
292
  const G = {
248
293
  map: forceMap || finalMap,
249
294
  players,
@@ -254,6 +299,9 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
254
299
  oilSupply,
255
300
  garbageSupply,
256
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,
257
305
  coalResupply,
258
306
  oilResupply,
259
307
  garbageResupply,
@@ -266,6 +314,17 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
266
314
  oilPrices,
267
315
  garbagePrices,
268
316
  uraniumPrices,
317
+ // Korea: parallel North-side fields. Undefined for non-Korea maps.
318
+ // Supply is shared with the primary `*Supply` fields above.
319
+ coalMarketNorth,
320
+ oilMarketNorth,
321
+ garbageMarketNorth,
322
+ coalResupplyNorth,
323
+ oilResupplyNorth,
324
+ garbageResupplyNorth,
325
+ coalPricesNorth,
326
+ oilPricesNorth,
327
+ garbagePricesNorth,
269
328
  actualMarket,
270
329
  futureMarket,
271
330
  chosenPowerPlant: undefined,
@@ -282,13 +341,22 @@ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original',
282
341
  auctionSkips: 0,
283
342
  citiesToStep2: (forceMap || finalMap).name == 'Baden-Württemberg'
284
343
  ? citiesToStep2BadenWurttemberg[numPlayers - 2]
285
- : citiesToStep2[numPlayers - 2],
344
+ : (forceMap || finalMap).name == 'UK & Ireland'
345
+ ? citiesToStep2UKIreland[numPlayers - 2]
346
+ : citiesToStep2[numPlayers - 2],
286
347
  citiesToEndGame: citiesToEndGame[numPlayers - 2],
287
348
  resourceResupply: [
288
349
  `[${coalResupply[p][0]}, ${oilResupply[p][0]}, ${garbageResupply[p][0]}, ${uraniumResupply[p][0]}]`,
289
350
  `[${coalResupply[p][1]}, ${oilResupply[p][1]}, ${garbageResupply[p][1]}, ${uraniumResupply[p][1]}]`,
290
351
  `[${coalResupply[p][2]}, ${oilResupply[p][2]}, ${garbageResupply[p][2]}, ${uraniumResupply[p][2]}]`,
291
352
  ],
353
+ resourceResupplyNorth: coalResupplyNorth && oilResupplyNorth && garbageResupplyNorth
354
+ ? [
355
+ `[${coalResupplyNorth[p][0]}, ${oilResupplyNorth[p][0]}, ${garbageResupplyNorth[p][0]}]`,
356
+ `[${coalResupplyNorth[p][1]}, ${oilResupplyNorth[p][1]}, ${garbageResupplyNorth[p][1]}]`,
357
+ `[${coalResupplyNorth[p][2]}, ${oilResupplyNorth[p][2]}, ${garbageResupplyNorth[p][2]}]`,
358
+ ]
359
+ : undefined,
292
360
  paymentTable: cityIncome,
293
361
  variant,
294
362
  minimunBid: 0,
@@ -340,7 +408,7 @@ function currentPlayers(G) {
340
408
  }
341
409
  exports.currentPlayers = currentPlayers;
342
410
  function move(G, move, playerNumber, isUndo = false) {
343
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
411
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
344
412
  const player = G.players[playerNumber];
345
413
  const available = (_a = player.availableMoves) === null || _a === void 0 ? void 0 : _a[move.name];
346
414
  updateGameState(G);
@@ -545,6 +613,11 @@ function move(G, move, playerNumber, isUndo = false) {
545
613
  else {
546
614
  player.passed = true;
547
615
  }
616
+ // Korea: end of this player's buying turn — clear the side lock
617
+ // so the next player can pick freely.
618
+ if (G.map.name == 'Korea') {
619
+ G.chosenSide = undefined;
620
+ }
548
621
  if (G.players.filter((p) => !p.passed && !p.isDropped).length == 0) {
549
622
  G.players.forEach((p) => {
550
623
  p.passed = p.isDropped;
@@ -589,7 +662,18 @@ function move(G, move, playerNumber, isUndo = false) {
589
662
  G.powerPlantsDeck.unshift(getPowerPlant(18));
590
663
  }
591
664
  }
592
- 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
+ }
593
677
  }
594
678
  }
595
679
  if (maxCities >= G.citiesToEndGame) {
@@ -656,9 +740,50 @@ function move(G, move, playerNumber, isUndo = false) {
656
740
  player.targetCitiesPowered = 0;
657
741
  }
658
742
  if (G.players.filter((p) => !p.passed && !p.isDropped).length == 0) {
659
- const coalResupplyValue = Math.min(G.coalSupply, G.coalResupply[G.players.length - 2][G.step - 1]);
660
- G.coalMarket += coalResupplyValue;
661
- G.coalSupply -= coalResupplyValue;
743
+ // Resupply is also capped by remaining market capacity (the
744
+ // prices array length minus current market size). Without
745
+ // this, smaller markets like Korea's can overflow past the
746
+ // number of slots and break price lookups.
747
+ const coalCapSouth = ((_d = (_c = G.coalPrices) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : prices_1.default[gamestate_1.ResourceType.Coal].length) - G.coalMarket;
748
+ const oilCapSouth = ((_f = (_e = G.oilPrices) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : prices_1.default[gamestate_1.ResourceType.Oil].length) - G.oilMarket;
749
+ const garbageCapSouth = ((_h = (_g = G.garbagePrices) === null || _g === void 0 ? void 0 : _g.length) !== null && _h !== void 0 ? _h : prices_1.default[gamestate_1.ResourceType.Garbage].length) - G.garbageMarket;
750
+ const uraniumCapSouth = ((_k = (_j = G.uraniumPrices) === null || _j === void 0 ? void 0 : _j.length) !== null && _k !== void 0 ? _k : prices_1.default[gamestate_1.ResourceType.Uranium].length) - G.uraniumMarket;
751
+ // Korea: North restocks FIRST from the shared supply pool, then
752
+ // South takes whatever remains. If supply runs short, South gets less.
753
+ let coalResupplyNorthValue = 0;
754
+ let oilResupplyNorthValue = 0;
755
+ let garbageResupplyNorthValue = 0;
756
+ if (G.coalResupplyNorth) {
757
+ const coalCapNorth = G.coalPricesNorth.length - G.coalMarketNorth;
758
+ const oilCapNorth = G.oilPricesNorth.length - G.oilMarketNorth;
759
+ const garbageCapNorth = G.garbagePricesNorth.length - G.garbageMarketNorth;
760
+ coalResupplyNorthValue = Math.min(G.coalSupply, G.coalResupplyNorth[G.players.length - 2][G.step - 1], coalCapNorth);
761
+ G.coalMarketNorth += coalResupplyNorthValue;
762
+ G.coalSupply -= coalResupplyNorthValue;
763
+ oilResupplyNorthValue = Math.min(G.oilSupply, G.oilResupplyNorth[G.players.length - 2][G.step - 1], oilCapNorth);
764
+ G.oilMarketNorth += oilResupplyNorthValue;
765
+ G.oilSupply -= oilResupplyNorthValue;
766
+ garbageResupplyNorthValue = Math.min(G.garbageSupply, G.garbageResupplyNorth[G.players.length - 2][G.step - 1], garbageCapNorth);
767
+ G.garbageMarketNorth += garbageResupplyNorthValue;
768
+ G.garbageSupply -= garbageResupplyNorthValue;
769
+ }
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
+ }
662
787
  let oilResupplyValue;
663
788
  if (G.map.name == 'Middle East') {
664
789
  if (G.oilMarket == 0) {
@@ -678,25 +803,33 @@ function move(G, move, playerNumber, isUndo = false) {
678
803
  }
679
804
  }
680
805
  else {
681
- oilResupplyValue = Math.min(G.oilSupply, G.oilResupply[G.players.length - 2][G.step - 1]);
806
+ oilResupplyValue = Math.min(G.oilSupply, G.oilResupply[G.players.length - 2][G.step - 1], oilCapSouth);
682
807
  G.oilMarket += oilResupplyValue;
683
808
  G.oilSupply -= oilResupplyValue;
684
809
  }
685
- const garbageResupplyValue = Math.min(G.garbageSupply, G.garbageResupply[G.players.length - 2][G.step - 1]);
810
+ const garbageResupplyValue = Math.min(G.garbageSupply, G.garbageResupply[G.players.length - 2][G.step - 1], garbageCapSouth);
686
811
  G.garbageMarket += garbageResupplyValue;
687
812
  G.garbageSupply -= garbageResupplyValue;
688
813
  let uraniumResupplyValue = 0;
689
814
  if (G.options.variant != 'recharged' ||
690
815
  (G.options.map != 'Germany' && G.options.map != 'Italy') ||
691
816
  !G.card39Bought) {
692
- uraniumResupplyValue = Math.min(G.uraniumSupply, G.uraniumResupply[G.players.length - 2][G.step - 1]);
817
+ uraniumResupplyValue = Math.min(G.uraniumSupply, G.uraniumResupply[G.players.length - 2][G.step - 1], uraniumCapSouth);
693
818
  G.uraniumMarket += uraniumResupplyValue;
694
819
  G.uraniumSupply -= uraniumResupplyValue;
695
820
  }
696
- G.log.push({
697
- type: 'event',
698
- event: `Resupplying resources: [${coalResupplyValue}, ${oilResupplyValue}, ${garbageResupplyValue}, ${uraniumResupplyValue}].`,
699
- });
821
+ if (G.coalResupplyNorth) {
822
+ G.log.push({
823
+ type: 'event',
824
+ event: `Resupplying resources — North: [${coalResupplyNorthValue}, ${oilResupplyNorthValue}, ${garbageResupplyNorthValue}], South: [${coalResupplyValue}, ${oilResupplyValue}, ${garbageResupplyValue}, ${uraniumResupplyValue}].`,
825
+ });
826
+ }
827
+ else {
828
+ G.log.push({
829
+ type: 'event',
830
+ event: `Resupplying resources: [${coalResupplyValue}, ${oilResupplyValue}, ${garbageResupplyValue}, ${uraniumResupplyValue}].`,
831
+ });
832
+ }
700
833
  if (G.map.name == 'Middle East' && G.step == 2 && G.futureMarket.length > 0) {
701
834
  // If we aren't about to enter step 3, discard top two plants instead of one.
702
835
  let powerPlantToPush = G.futureMarket.pop();
@@ -968,17 +1101,35 @@ function move(G, move, playerNumber, isUndo = false) {
968
1101
  case move_1.MoveName.BuyResource: {
969
1102
  utils_1.asserts(move);
970
1103
  G.chosenResource = move.data.resource;
1104
+ // Korea: lock the player to the side of their first buy this turn.
1105
+ // Subsequent buys must come from the same side until they pass.
1106
+ if (move.data.side) {
1107
+ G.chosenSide = move.data.side;
1108
+ }
1109
+ const isNorth = move.data.side === 'north';
971
1110
  let price;
972
1111
  switch (move.data.resource) {
973
1112
  case gamestate_1.ResourceType.Coal: {
974
- if (G.coalMarket == 0) {
1113
+ if (isNorth) {
1114
+ const coalPrices = G.coalPricesNorth;
1115
+ price = coalPrices[coalPrices.length - G.coalMarketNorth];
1116
+ player.coalLeft++;
1117
+ G.coalMarketNorth--;
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
+ }
1125
+ else if (G.coalMarket == 0) {
975
1126
  price = 8;
976
1127
  player.coalLeft++;
977
1128
  G.coalSupply--;
978
1129
  move.fromSupply = true;
979
1130
  }
980
1131
  else {
981
- const coalPrices = (_c = G.coalPrices) !== null && _c !== void 0 ? _c : prices_1.default[gamestate_1.ResourceType.Coal];
1132
+ const coalPrices = (_l = G.coalPrices) !== null && _l !== void 0 ? _l : prices_1.default[gamestate_1.ResourceType.Coal];
982
1133
  price = coalPrices[coalPrices.length - G.coalMarket];
983
1134
  player.coalLeft++;
984
1135
  G.coalMarket--;
@@ -986,28 +1137,45 @@ function move(G, move, playerNumber, isUndo = false) {
986
1137
  break;
987
1138
  }
988
1139
  case gamestate_1.ResourceType.Oil: {
989
- const oilPrices = (_d = G.oilPrices) !== null && _d !== void 0 ? _d : prices_1.default[gamestate_1.ResourceType.Oil];
990
- price = oilPrices[oilPrices.length - G.oilMarket];
991
- player.oilLeft++;
992
- G.oilMarket--;
1140
+ if (isNorth) {
1141
+ const oilPrices = G.oilPricesNorth;
1142
+ price = oilPrices[oilPrices.length - G.oilMarketNorth];
1143
+ player.oilLeft++;
1144
+ G.oilMarketNorth--;
1145
+ }
1146
+ else {
1147
+ const oilPrices = (_m = G.oilPrices) !== null && _m !== void 0 ? _m : prices_1.default[gamestate_1.ResourceType.Oil];
1148
+ price = oilPrices[oilPrices.length - G.oilMarket];
1149
+ player.oilLeft++;
1150
+ G.oilMarket--;
1151
+ }
993
1152
  break;
994
1153
  }
995
1154
  case gamestate_1.ResourceType.Garbage: {
996
- const garbagePrices = (_e = G.garbagePrices) !== null && _e !== void 0 ? _e : prices_1.default[gamestate_1.ResourceType.Garbage];
997
- price = garbagePrices[garbagePrices.length - G.garbageMarket];
998
- // $1 cheaper for players in Wien in Central Europe
999
- if (G.map.name == 'Central Europe') {
1000
- const wienCity = player.cities.filter((c) => c.name == 'Wien');
1001
- if ((wienCity === null || wienCity === void 0 ? void 0 : wienCity.length) > 0) {
1002
- price--;
1155
+ if (isNorth) {
1156
+ const garbagePrices = G.garbagePricesNorth;
1157
+ price = garbagePrices[garbagePrices.length - G.garbageMarketNorth];
1158
+ player.garbageLeft++;
1159
+ G.garbageMarketNorth--;
1160
+ }
1161
+ else {
1162
+ const garbagePrices = (_o = G.garbagePrices) !== null && _o !== void 0 ? _o : prices_1.default[gamestate_1.ResourceType.Garbage];
1163
+ price = garbagePrices[garbagePrices.length - G.garbageMarket];
1164
+ // $1 cheaper for players in Wien in Central Europe
1165
+ if (G.map.name == 'Central Europe') {
1166
+ const wienCity = player.cities.filter((c) => c.name == 'Wien');
1167
+ if ((wienCity === null || wienCity === void 0 ? void 0 : wienCity.length) > 0) {
1168
+ price--;
1169
+ }
1003
1170
  }
1171
+ player.garbageLeft++;
1172
+ G.garbageMarket--;
1004
1173
  }
1005
- player.garbageLeft++;
1006
- G.garbageMarket--;
1007
1174
  break;
1008
1175
  }
1009
1176
  case gamestate_1.ResourceType.Uranium: {
1010
- const uraniumPrices = (_f = G.uraniumPrices) !== null && _f !== void 0 ? _f : prices_1.default[gamestate_1.ResourceType.Uranium];
1177
+ // Uranium is only available from the South market (or non-Korea maps).
1178
+ const uraniumPrices = (_p = G.uraniumPrices) !== null && _p !== void 0 ? _p : prices_1.default[gamestate_1.ResourceType.Uranium];
1011
1179
  price = uraniumPrices[uraniumPrices.length - G.uraniumMarket];
1012
1180
  player.uraniumLeft++;
1013
1181
  G.uraniumMarket--;
@@ -1033,8 +1201,15 @@ function move(G, move, playerNumber, isUndo = false) {
1033
1201
  player.cities.push({ name: move.data.name, position });
1034
1202
  player.money -= move.data.price;
1035
1203
  if (G.options.trackTotalSpent) {
1036
- player.totalSpentCities += 10 + position * 5;
1037
- 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
+ }
1038
1213
  }
1039
1214
  G.log.push({
1040
1215
  type: 'move',
@@ -1065,7 +1240,13 @@ function move(G, move, playerNumber, isUndo = false) {
1065
1240
  switch (resourceType) {
1066
1241
  case gamestate_1.ResourceType.Coal:
1067
1242
  player.coalLeft--;
1068
- 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
+ }
1069
1250
  break;
1070
1251
  case gamestate_1.ResourceType.Oil:
1071
1252
  player.oilLeft--;
@@ -1109,10 +1290,22 @@ function move(G, move, playerNumber, isUndo = false) {
1109
1290
  break;
1110
1291
  }
1111
1292
  case move_1.MoveName.BuyResource: {
1293
+ const undoIsNorth = lastMove.data.side === 'north';
1112
1294
  let price;
1113
1295
  switch (lastMove.data.resource) {
1114
1296
  case gamestate_1.ResourceType.Coal:
1115
- if (lastMove.fromSupply) {
1297
+ if (undoIsNorth) {
1298
+ player.coalLeft--;
1299
+ G.coalMarketNorth++;
1300
+ const coalPrices = G.coalPricesNorth;
1301
+ price = coalPrices[coalPrices.length - G.coalMarketNorth];
1302
+ }
1303
+ else if (lastMove.data.fromStorage) {
1304
+ price = 8;
1305
+ player.coalLeft--;
1306
+ G.coalStorage++;
1307
+ }
1308
+ else if (lastMove.fromSupply) {
1116
1309
  price = 8;
1117
1310
  player.coalLeft--;
1118
1311
  G.coalSupply++;
@@ -1120,35 +1313,52 @@ function move(G, move, playerNumber, isUndo = false) {
1120
1313
  else {
1121
1314
  player.coalLeft--;
1122
1315
  G.coalMarket++;
1123
- const coalPrices = (_g = G.coalPrices) !== null && _g !== void 0 ? _g : prices_1.default[gamestate_1.ResourceType.Coal];
1316
+ const coalPrices = (_q = G.coalPrices) !== null && _q !== void 0 ? _q : prices_1.default[gamestate_1.ResourceType.Coal];
1124
1317
  price = coalPrices[coalPrices.length - G.coalMarket];
1125
1318
  }
1126
1319
  break;
1127
1320
  case gamestate_1.ResourceType.Oil: {
1128
- player.oilLeft--;
1129
- G.oilMarket++;
1130
- const oilPrices = (_h = G.oilPrices) !== null && _h !== void 0 ? _h : prices_1.default[gamestate_1.ResourceType.Oil];
1131
- price = oilPrices[oilPrices.length - G.oilMarket];
1321
+ if (undoIsNorth) {
1322
+ player.oilLeft--;
1323
+ G.oilMarketNorth++;
1324
+ const oilPrices = G.oilPricesNorth;
1325
+ price = oilPrices[oilPrices.length - G.oilMarketNorth];
1326
+ }
1327
+ else {
1328
+ player.oilLeft--;
1329
+ G.oilMarket++;
1330
+ const oilPrices = (_r = G.oilPrices) !== null && _r !== void 0 ? _r : prices_1.default[gamestate_1.ResourceType.Oil];
1331
+ price = oilPrices[oilPrices.length - G.oilMarket];
1332
+ }
1132
1333
  break;
1133
1334
  }
1134
1335
  case gamestate_1.ResourceType.Garbage: {
1135
- player.garbageLeft--;
1136
- G.garbageMarket++;
1137
- const garbagePrices = (_j = G.garbagePrices) !== null && _j !== void 0 ? _j : prices_1.default[gamestate_1.ResourceType.Garbage];
1138
- price = garbagePrices[garbagePrices.length - G.garbageMarket];
1139
- // $1 cheaper for players in Wien in Central Europe
1140
- if (G.map.name == 'Central Europe') {
1141
- const wienCity = player.cities.filter((c) => c.name == 'Wien');
1142
- if ((wienCity === null || wienCity === void 0 ? void 0 : wienCity.length) > 0) {
1143
- price--;
1336
+ if (undoIsNorth) {
1337
+ player.garbageLeft--;
1338
+ G.garbageMarketNorth++;
1339
+ const garbagePrices = G.garbagePricesNorth;
1340
+ price = garbagePrices[garbagePrices.length - G.garbageMarketNorth];
1341
+ }
1342
+ else {
1343
+ player.garbageLeft--;
1344
+ G.garbageMarket++;
1345
+ const garbagePrices = (_s = G.garbagePrices) !== null && _s !== void 0 ? _s : prices_1.default[gamestate_1.ResourceType.Garbage];
1346
+ price = garbagePrices[garbagePrices.length - G.garbageMarket];
1347
+ // $1 cheaper for players in Wien in Central Europe
1348
+ if (G.map.name == 'Central Europe') {
1349
+ const wienCity = player.cities.filter((c) => c.name == 'Wien');
1350
+ if ((wienCity === null || wienCity === void 0 ? void 0 : wienCity.length) > 0) {
1351
+ price--;
1352
+ }
1144
1353
  }
1145
1354
  }
1146
1355
  break;
1147
1356
  }
1148
1357
  case gamestate_1.ResourceType.Uranium: {
1358
+ // Uranium is only available from the South market (or non-Korea maps).
1149
1359
  player.uraniumLeft--;
1150
1360
  G.uraniumMarket++;
1151
- const uraniumPrices = (_k = G.uraniumPrices) !== null && _k !== void 0 ? _k : prices_1.default[gamestate_1.ResourceType.Uranium];
1361
+ const uraniumPrices = (_t = G.uraniumPrices) !== null && _t !== void 0 ? _t : prices_1.default[gamestate_1.ResourceType.Uranium];
1152
1362
  price = uraniumPrices[uraniumPrices.length - G.uraniumMarket];
1153
1363
  break;
1154
1364
  }
@@ -1161,6 +1371,22 @@ function move(G, move, playerNumber, isUndo = false) {
1161
1371
  G.chosenResource = undefined;
1162
1372
  }
1163
1373
  G.log.pop();
1374
+ // Korea: keep chosenSide locked while the player still has
1375
+ // outstanding BuyResource moves this phase, but clear it once
1376
+ // the last one is undone so they can switch sides again.
1377
+ if (G.map.name == 'Korea' && G.chosenSide) {
1378
+ let stillCommitted = false;
1379
+ for (let i = G.log.length - 1; i >= 0; i--) {
1380
+ const entry = G.log[i];
1381
+ if (entry.type !== 'move')
1382
+ continue;
1383
+ stillCommitted = entry.player === playerNumber && entry.move.name === move_1.MoveName.BuyResource;
1384
+ break;
1385
+ }
1386
+ if (!stillCommitted) {
1387
+ G.chosenSide = undefined;
1388
+ }
1389
+ }
1164
1390
  break;
1165
1391
  }
1166
1392
  case move_1.MoveName.Build: {
@@ -1905,6 +2131,13 @@ function updateGameState(G) {
1905
2131
  `[${G.coalResupply[p][1]}, ${G.oilResupply[p][1]}, ${G.garbageResupply[p][1]}, ${G.uraniumResupply[p][1]}]`,
1906
2132
  `[${G.coalResupply[p][2]}, ${G.oilResupply[p][2]}, ${G.garbageResupply[p][2]}, ${G.uraniumResupply[p][2]}]`,
1907
2133
  ];
2134
+ if (G.coalResupplyNorth && G.oilResupplyNorth && G.garbageResupplyNorth) {
2135
+ G.resourceResupplyNorth = [
2136
+ `[${G.coalResupplyNorth[p][0]}, ${G.oilResupplyNorth[p][0]}, ${G.garbageResupplyNorth[p][0]}]`,
2137
+ `[${G.coalResupplyNorth[p][1]}, ${G.oilResupplyNorth[p][1]}, ${G.garbageResupplyNorth[p][1]}]`,
2138
+ `[${G.coalResupplyNorth[p][2]}, ${G.oilResupplyNorth[p][2]}, ${G.garbageResupplyNorth[p][2]}]`,
2139
+ ];
2140
+ }
1908
2141
  }
1909
2142
  }
1910
2143
  function fastAuction(G, player, bid) {
@@ -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';
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;
@@ -100,6 +101,15 @@ export interface GameState {
100
101
  oilResupply?: number[][];
101
102
  garbageResupply?: number[][];
102
103
  uraniumResupply?: number[][];
104
+ coalMarketNorth?: number;
105
+ oilMarketNorth?: number;
106
+ garbageMarketNorth?: number;
107
+ coalResupplyNorth?: number[][];
108
+ oilResupplyNorth?: number[][];
109
+ garbageResupplyNorth?: number[][];
110
+ coalPricesNorth?: number[];
111
+ oilPricesNorth?: number[];
112
+ garbagePricesNorth?: number[];
103
113
  coalPrices?: number[];
104
114
  oilPrices?: number[];
105
115
  garbagePrices?: number[];
@@ -108,6 +118,7 @@ export interface GameState {
108
118
  futureMarket: PowerPlant[];
109
119
  chosenPowerPlant: PowerPlant | undefined;
110
120
  chosenResource?: ResourceType | undefined;
121
+ chosenSide?: 'north' | 'south';
111
122
  currentBid: number | undefined;
112
123
  auctioningPlayer: number | undefined;
113
124
  step: number;
@@ -122,6 +133,7 @@ export interface GameState {
122
133
  citiesToEndGame: number;
123
134
  citiesBuiltInCurrentRound?: number;
124
135
  resourceResupply: string[];
136
+ resourceResupplyNorth?: string[];
125
137
  paymentTable: number[];
126
138
  minimunBid: number;
127
139
  plantDiscountActive: boolean;