powergrid-engine 1.9.9 → 1.11.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.
package/src/engine.ts CHANGED
@@ -161,6 +161,15 @@ export function setup(
161
161
  let oilResupply: number[][];
162
162
  let garbageResupply: number[][];
163
163
  let uraniumResupply: number[][];
164
+ // Korea: parallel North-side resupply tables (no uranium row).
165
+ let coalResupplyNorth: number[][] | undefined;
166
+ let oilResupplyNorth: number[][] | undefined;
167
+ let garbageResupplyNorth: number[][] | undefined;
168
+ if (chosenMap.resupplyNorth) {
169
+ coalResupplyNorth = chosenMap.resupplyNorth[0];
170
+ oilResupplyNorth = chosenMap.resupplyNorth[1];
171
+ garbageResupplyNorth = chosenMap.resupplyNorth[2];
172
+ }
164
173
  if (chosenMap.resupply) {
165
174
  coalResupply = chosenMap.resupply[0];
166
175
  oilResupply = chosenMap.resupply[1];
@@ -273,6 +282,23 @@ export function setup(
273
282
  );
274
283
 
275
284
  finalMap = filteredMap;
285
+
286
+ if (chosenMap.regionalPowerPlants) {
287
+ for (const region of playRegions) {
288
+ const replacements = chosenMap.regionalPowerPlants[region];
289
+ if (replacements) {
290
+ for (const newPlant of replacements) {
291
+ const swapIn = (arr: PowerPlant[]) => {
292
+ const idx = arr.findIndex((p) => p.number === newPlant.number);
293
+ if (idx !== -1) arr[idx] = { ...newPlant };
294
+ };
295
+ swapIn(actualMarket);
296
+ swapIn(futureMarket);
297
+ swapIn(powerPlantsDeck);
298
+ }
299
+ }
300
+ }
301
+ }
276
302
  }
277
303
 
278
304
  const coalMarket = chosenMap.startingResources ? chosenMap.startingResources[0] : 24;
@@ -285,9 +311,17 @@ export function setup(
285
311
  const totalGarbage = chosenMap.startingSupply ? chosenMap.startingSupply[2] : 24;
286
312
  const totalUranium = chosenMap.startingSupply ? chosenMap.startingSupply[3] : 12;
287
313
 
288
- const coalSupply = totalCoal - coalMarket;
289
- const oilSupply = totalOil - oilMarket;
290
- const garbageSupply = totalGarbage - garbageMarket;
314
+ // Korea: parallel North-side market and prices. Supply is shared — see below.
315
+ const coalMarketNorth = chosenMap.startingResourcesNorth?.[0];
316
+ const oilMarketNorth = chosenMap.startingResourcesNorth?.[1];
317
+ const garbageMarketNorth = chosenMap.startingResourcesNorth?.[2];
318
+
319
+ // Supply pools are shared between both sides for Korea. `startingSupply`
320
+ // represents the TOTAL cubes in the game; the supply pool is whatever is
321
+ // left after both markets are filled.
322
+ const coalSupply = totalCoal - coalMarket - (coalMarketNorth ?? 0);
323
+ const oilSupply = totalOil - oilMarket - (oilMarketNorth ?? 0);
324
+ const garbageSupply = totalGarbage - garbageMarket - (garbageMarketNorth ?? 0);
291
325
  const uraniumSupply = totalUranium - uraniumMarket;
292
326
 
293
327
  const coalPrices = cloneDeep(chosenMap.coalPrices ?? prices.coal);
@@ -295,6 +329,10 @@ export function setup(
295
329
  const garbagePrices = cloneDeep(chosenMap.garbagePrices ?? prices.garbage);
296
330
  const uraniumPrices = cloneDeep(chosenMap.uraniumPrices ?? prices.uranium);
297
331
 
332
+ const coalPricesNorth = chosenMap.coalPricesNorth ? cloneDeep(chosenMap.coalPricesNorth) : undefined;
333
+ const oilPricesNorth = chosenMap.oilPricesNorth ? cloneDeep(chosenMap.oilPricesNorth) : undefined;
334
+ const garbagePricesNorth = chosenMap.garbagePricesNorth ? cloneDeep(chosenMap.garbagePricesNorth) : undefined;
335
+
298
336
  const G: GameState = {
299
337
  map: forceMap || finalMap,
300
338
  players,
@@ -317,6 +355,17 @@ export function setup(
317
355
  oilPrices,
318
356
  garbagePrices,
319
357
  uraniumPrices,
358
+ // Korea: parallel North-side fields. Undefined for non-Korea maps.
359
+ // Supply is shared with the primary `*Supply` fields above.
360
+ coalMarketNorth,
361
+ oilMarketNorth,
362
+ garbageMarketNorth,
363
+ coalResupplyNorth,
364
+ oilResupplyNorth,
365
+ garbageResupplyNorth,
366
+ coalPricesNorth,
367
+ oilPricesNorth,
368
+ garbagePricesNorth,
320
369
  actualMarket,
321
370
  futureMarket,
322
371
  chosenPowerPlant: undefined,
@@ -341,6 +390,14 @@ export function setup(
341
390
  `[${coalResupply[p][1]}, ${oilResupply[p][1]}, ${garbageResupply[p][1]}, ${uraniumResupply[p][1]}]`,
342
391
  `[${coalResupply[p][2]}, ${oilResupply[p][2]}, ${garbageResupply[p][2]}, ${uraniumResupply[p][2]}]`,
343
392
  ],
393
+ resourceResupplyNorth:
394
+ coalResupplyNorth && oilResupplyNorth && garbageResupplyNorth
395
+ ? [
396
+ `[${coalResupplyNorth[p][0]}, ${oilResupplyNorth[p][0]}, ${garbageResupplyNorth[p][0]}]`,
397
+ `[${coalResupplyNorth[p][1]}, ${oilResupplyNorth[p][1]}, ${garbageResupplyNorth[p][1]}]`,
398
+ `[${coalResupplyNorth[p][2]}, ${oilResupplyNorth[p][2]}, ${garbageResupplyNorth[p][2]}]`,
399
+ ]
400
+ : undefined,
344
401
  paymentTable: cityIncome,
345
402
  variant,
346
403
  minimunBid: 0,
@@ -640,6 +697,12 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
640
697
  player.passed = true;
641
698
  }
642
699
 
700
+ // Korea: end of this player's buying turn — clear the side lock
701
+ // so the next player can pick freely.
702
+ if (G.map.name == 'Korea') {
703
+ G.chosenSide = undefined;
704
+ }
705
+
643
706
  if (G.players.filter((p) => !p.passed && !p.isDropped).length == 0) {
644
707
  G.players.forEach((p) => {
645
708
  p.passed = p.isDropped;
@@ -774,9 +837,56 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
774
837
  }
775
838
 
776
839
  if (G.players.filter((p) => !p.passed && !p.isDropped).length == 0) {
840
+ // Resupply is also capped by remaining market capacity (the
841
+ // prices array length minus current market size). Without
842
+ // this, smaller markets like Korea's can overflow past the
843
+ // number of slots and break price lookups.
844
+ const coalCapSouth = (G.coalPrices?.length ?? prices[ResourceType.Coal].length) - G.coalMarket;
845
+ const oilCapSouth = (G.oilPrices?.length ?? prices[ResourceType.Oil].length) - G.oilMarket;
846
+ const garbageCapSouth =
847
+ (G.garbagePrices?.length ?? prices[ResourceType.Garbage].length) - G.garbageMarket;
848
+ const uraniumCapSouth =
849
+ (G.uraniumPrices?.length ?? prices[ResourceType.Uranium].length) - G.uraniumMarket;
850
+
851
+ // Korea: North restocks FIRST from the shared supply pool, then
852
+ // South takes whatever remains. If supply runs short, South gets less.
853
+ let coalResupplyNorthValue = 0;
854
+ let oilResupplyNorthValue = 0;
855
+ let garbageResupplyNorthValue = 0;
856
+ if (G.coalResupplyNorth) {
857
+ const coalCapNorth = G.coalPricesNorth!.length - G.coalMarketNorth!;
858
+ const oilCapNorth = G.oilPricesNorth!.length - G.oilMarketNorth!;
859
+ const garbageCapNorth = G.garbagePricesNorth!.length - G.garbageMarketNorth!;
860
+
861
+ coalResupplyNorthValue = Math.min(
862
+ G.coalSupply,
863
+ G.coalResupplyNorth[G.players.length - 2][G.step - 1],
864
+ coalCapNorth
865
+ );
866
+ G.coalMarketNorth! += coalResupplyNorthValue;
867
+ G.coalSupply -= coalResupplyNorthValue;
868
+
869
+ oilResupplyNorthValue = Math.min(
870
+ G.oilSupply,
871
+ G.oilResupplyNorth![G.players.length - 2][G.step - 1],
872
+ oilCapNorth
873
+ );
874
+ G.oilMarketNorth! += oilResupplyNorthValue;
875
+ G.oilSupply -= oilResupplyNorthValue;
876
+
877
+ garbageResupplyNorthValue = Math.min(
878
+ G.garbageSupply,
879
+ G.garbageResupplyNorth![G.players.length - 2][G.step - 1],
880
+ garbageCapNorth
881
+ );
882
+ G.garbageMarketNorth! += garbageResupplyNorthValue;
883
+ G.garbageSupply -= garbageResupplyNorthValue;
884
+ }
885
+
777
886
  const coalResupplyValue = Math.min(
778
887
  G.coalSupply,
779
- G.coalResupply![G.players.length - 2][G.step - 1]
888
+ G.coalResupply![G.players.length - 2][G.step - 1],
889
+ coalCapSouth
780
890
  );
781
891
  G.coalMarket += coalResupplyValue;
782
892
  G.coalSupply -= coalResupplyValue;
@@ -799,14 +909,19 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
799
909
  }
800
910
  }
801
911
  } else {
802
- oilResupplyValue = Math.min(G.oilSupply, G.oilResupply![G.players.length - 2][G.step - 1]);
912
+ oilResupplyValue = Math.min(
913
+ G.oilSupply,
914
+ G.oilResupply![G.players.length - 2][G.step - 1],
915
+ oilCapSouth
916
+ );
803
917
  G.oilMarket += oilResupplyValue;
804
918
  G.oilSupply -= oilResupplyValue;
805
919
  }
806
920
 
807
921
  const garbageResupplyValue = Math.min(
808
922
  G.garbageSupply,
809
- G.garbageResupply![G.players.length - 2][G.step - 1]
923
+ G.garbageResupply![G.players.length - 2][G.step - 1],
924
+ garbageCapSouth
810
925
  );
811
926
  G.garbageMarket += garbageResupplyValue;
812
927
  G.garbageSupply -= garbageResupplyValue;
@@ -819,16 +934,24 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
819
934
  ) {
820
935
  uraniumResupplyValue = Math.min(
821
936
  G.uraniumSupply,
822
- G.uraniumResupply![G.players.length - 2][G.step - 1]
937
+ G.uraniumResupply![G.players.length - 2][G.step - 1],
938
+ uraniumCapSouth
823
939
  );
824
940
  G.uraniumMarket += uraniumResupplyValue;
825
941
  G.uraniumSupply -= uraniumResupplyValue;
826
942
  }
827
943
 
828
- G.log.push({
829
- type: 'event',
830
- event: `Resupplying resources: [${coalResupplyValue}, ${oilResupplyValue}, ${garbageResupplyValue}, ${uraniumResupplyValue}].`,
831
- });
944
+ if (G.coalResupplyNorth) {
945
+ G.log.push({
946
+ type: 'event',
947
+ event: `Resupplying resources — North: [${coalResupplyNorthValue}, ${oilResupplyNorthValue}, ${garbageResupplyNorthValue}], South: [${coalResupplyValue}, ${oilResupplyValue}, ${garbageResupplyValue}, ${uraniumResupplyValue}].`,
948
+ });
949
+ } else {
950
+ G.log.push({
951
+ type: 'event',
952
+ event: `Resupplying resources: [${coalResupplyValue}, ${oilResupplyValue}, ${garbageResupplyValue}, ${uraniumResupplyValue}].`,
953
+ });
954
+ }
832
955
 
833
956
  if (G.map.name == 'Middle East' && G.step == 2 && G.futureMarket.length > 0) {
834
957
  // If we aren't about to enter step 3, discard top two plants instead of one.
@@ -1145,10 +1268,23 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
1145
1268
  asserts<Moves.MoveBuyResource>(move);
1146
1269
  G.chosenResource = move.data.resource;
1147
1270
 
1271
+ // Korea: lock the player to the side of their first buy this turn.
1272
+ // Subsequent buys must come from the same side until they pass.
1273
+ if (move.data.side) {
1274
+ G.chosenSide = move.data.side;
1275
+ }
1276
+
1277
+ const isNorth = move.data.side === 'north';
1278
+
1148
1279
  let price: number;
1149
1280
  switch (move.data.resource) {
1150
1281
  case ResourceType.Coal: {
1151
- if (G.coalMarket == 0) {
1282
+ if (isNorth) {
1283
+ const coalPrices = G.coalPricesNorth!;
1284
+ price = coalPrices[coalPrices.length - G.coalMarketNorth!];
1285
+ player.coalLeft++;
1286
+ G.coalMarketNorth!--;
1287
+ } else if (G.coalMarket == 0) {
1152
1288
  price = 8;
1153
1289
  player.coalLeft++;
1154
1290
  G.coalSupply--;
@@ -1164,31 +1300,46 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
1164
1300
  }
1165
1301
 
1166
1302
  case ResourceType.Oil: {
1167
- const oilPrices = G.oilPrices ?? prices[ResourceType.Oil];
1168
- price = oilPrices[oilPrices.length - G.oilMarket];
1169
- player.oilLeft++;
1170
- G.oilMarket--;
1303
+ if (isNorth) {
1304
+ const oilPrices = G.oilPricesNorth!;
1305
+ price = oilPrices[oilPrices.length - G.oilMarketNorth!];
1306
+ player.oilLeft++;
1307
+ G.oilMarketNorth!--;
1308
+ } else {
1309
+ const oilPrices = G.oilPrices ?? prices[ResourceType.Oil];
1310
+ price = oilPrices[oilPrices.length - G.oilMarket];
1311
+ player.oilLeft++;
1312
+ G.oilMarket--;
1313
+ }
1171
1314
  break;
1172
1315
  }
1173
1316
 
1174
1317
  case ResourceType.Garbage: {
1175
- const garbagePrices = G.garbagePrices ?? prices[ResourceType.Garbage];
1176
- price = garbagePrices[garbagePrices.length - G.garbageMarket];
1177
-
1178
- // $1 cheaper for players in Wien in Central Europe
1179
- if (G.map.name == 'Central Europe') {
1180
- const wienCity = player.cities.filter((c) => c.name == 'Wien');
1181
- if (wienCity?.length > 0) {
1182
- price--;
1318
+ if (isNorth) {
1319
+ const garbagePrices = G.garbagePricesNorth!;
1320
+ price = garbagePrices[garbagePrices.length - G.garbageMarketNorth!];
1321
+ player.garbageLeft++;
1322
+ G.garbageMarketNorth!--;
1323
+ } else {
1324
+ const garbagePrices = G.garbagePrices ?? prices[ResourceType.Garbage];
1325
+ price = garbagePrices[garbagePrices.length - G.garbageMarket];
1326
+
1327
+ // $1 cheaper for players in Wien in Central Europe
1328
+ if (G.map.name == 'Central Europe') {
1329
+ const wienCity = player.cities.filter((c) => c.name == 'Wien');
1330
+ if (wienCity?.length > 0) {
1331
+ price--;
1332
+ }
1183
1333
  }
1184
- }
1185
1334
 
1186
- player.garbageLeft++;
1187
- G.garbageMarket--;
1335
+ player.garbageLeft++;
1336
+ G.garbageMarket--;
1337
+ }
1188
1338
  break;
1189
1339
  }
1190
1340
 
1191
1341
  case ResourceType.Uranium: {
1342
+ // Uranium is only available from the South market (or non-Korea maps).
1192
1343
  const uraniumPrices = G.uraniumPrices ?? prices[ResourceType.Uranium];
1193
1344
  price = uraniumPrices[uraniumPrices.length - G.uraniumMarket];
1194
1345
  player.uraniumLeft++;
@@ -1324,10 +1475,16 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
1324
1475
  }
1325
1476
 
1326
1477
  case MoveName.BuyResource: {
1478
+ const undoIsNorth = lastMove.data.side === 'north';
1327
1479
  let price: number;
1328
1480
  switch (lastMove.data.resource) {
1329
1481
  case ResourceType.Coal:
1330
- if (lastMove.fromSupply) {
1482
+ if (undoIsNorth) {
1483
+ player.coalLeft--;
1484
+ G.coalMarketNorth!++;
1485
+ const coalPrices = G.coalPricesNorth!;
1486
+ price = coalPrices[coalPrices.length - G.coalMarketNorth!];
1487
+ } else if (lastMove.fromSupply) {
1331
1488
  price = 8;
1332
1489
  player.coalLeft--;
1333
1490
  G.coalSupply++;
@@ -1341,24 +1498,38 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
1341
1498
  break;
1342
1499
 
1343
1500
  case ResourceType.Oil: {
1344
- player.oilLeft--;
1345
- G.oilMarket++;
1346
- const oilPrices = G.oilPrices ?? prices[ResourceType.Oil];
1347
- price = oilPrices[oilPrices.length - G.oilMarket];
1501
+ if (undoIsNorth) {
1502
+ player.oilLeft--;
1503
+ G.oilMarketNorth!++;
1504
+ const oilPrices = G.oilPricesNorth!;
1505
+ price = oilPrices[oilPrices.length - G.oilMarketNorth!];
1506
+ } else {
1507
+ player.oilLeft--;
1508
+ G.oilMarket++;
1509
+ const oilPrices = G.oilPrices ?? prices[ResourceType.Oil];
1510
+ price = oilPrices[oilPrices.length - G.oilMarket];
1511
+ }
1348
1512
  break;
1349
1513
  }
1350
1514
 
1351
1515
  case ResourceType.Garbage: {
1352
- player.garbageLeft--;
1353
- G.garbageMarket++;
1354
- const garbagePrices = G.garbagePrices ?? prices[ResourceType.Garbage];
1355
- price = garbagePrices[garbagePrices.length - G.garbageMarket];
1356
-
1357
- // $1 cheaper for players in Wien in Central Europe
1358
- if (G.map.name == 'Central Europe') {
1359
- const wienCity = player.cities.filter((c) => c.name == 'Wien');
1360
- if (wienCity?.length > 0) {
1361
- price--;
1516
+ if (undoIsNorth) {
1517
+ player.garbageLeft--;
1518
+ G.garbageMarketNorth!++;
1519
+ const garbagePrices = G.garbagePricesNorth!;
1520
+ price = garbagePrices[garbagePrices.length - G.garbageMarketNorth!];
1521
+ } else {
1522
+ player.garbageLeft--;
1523
+ G.garbageMarket++;
1524
+ const garbagePrices = G.garbagePrices ?? prices[ResourceType.Garbage];
1525
+ price = garbagePrices[garbagePrices.length - G.garbageMarket];
1526
+
1527
+ // $1 cheaper for players in Wien in Central Europe
1528
+ if (G.map.name == 'Central Europe') {
1529
+ const wienCity = player.cities.filter((c) => c.name == 'Wien');
1530
+ if (wienCity?.length > 0) {
1531
+ price--;
1532
+ }
1362
1533
  }
1363
1534
  }
1364
1535
 
@@ -1366,6 +1537,7 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
1366
1537
  }
1367
1538
 
1368
1539
  case ResourceType.Uranium: {
1540
+ // Uranium is only available from the South market (or non-Korea maps).
1369
1541
  player.uraniumLeft--;
1370
1542
  G.uraniumMarket++;
1371
1543
  const uraniumPrices = G.uraniumPrices ?? prices[ResourceType.Uranium];
@@ -1385,6 +1557,22 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
1385
1557
  }
1386
1558
 
1387
1559
  G.log.pop();
1560
+
1561
+ // Korea: keep chosenSide locked while the player still has
1562
+ // outstanding BuyResource moves this phase, but clear it once
1563
+ // the last one is undone so they can switch sides again.
1564
+ if (G.map.name == 'Korea' && G.chosenSide) {
1565
+ let stillCommitted = false;
1566
+ for (let i = G.log.length - 1; i >= 0; i--) {
1567
+ const entry = G.log[i];
1568
+ if (entry.type !== 'move') continue;
1569
+ stillCommitted = entry.player === playerNumber && entry.move.name === MoveName.BuyResource;
1570
+ break;
1571
+ }
1572
+ if (!stillCommitted) {
1573
+ G.chosenSide = undefined;
1574
+ }
1575
+ }
1388
1576
  break;
1389
1577
  }
1390
1578
 
@@ -2229,6 +2417,13 @@ function updateGameState(G: GameState) {
2229
2417
  `[${G.coalResupply[p][1]}, ${G.oilResupply[p][1]}, ${G.garbageResupply[p][1]}, ${G.uraniumResupply[p][1]}]`,
2230
2418
  `[${G.coalResupply[p][2]}, ${G.oilResupply[p][2]}, ${G.garbageResupply[p][2]}, ${G.uraniumResupply[p][2]}]`,
2231
2419
  ];
2420
+ if (G.coalResupplyNorth && G.oilResupplyNorth && G.garbageResupplyNorth) {
2421
+ G.resourceResupplyNorth = [
2422
+ `[${G.coalResupplyNorth[p][0]}, ${G.oilResupplyNorth[p][0]}, ${G.garbageResupplyNorth[p][0]}]`,
2423
+ `[${G.coalResupplyNorth[p][1]}, ${G.oilResupplyNorth[p][1]}, ${G.garbageResupplyNorth[p][1]}]`,
2424
+ `[${G.coalResupplyNorth[p][2]}, ${G.oilResupplyNorth[p][2]}, ${G.garbageResupplyNorth[p][2]}]`,
2425
+ ];
2426
+ }
2232
2427
  }
2233
2428
  }
2234
2429
 
package/src/gamestate.ts CHANGED
@@ -16,12 +16,12 @@ export type MapName =
16
16
  | 'China'
17
17
  | 'Benelux'
18
18
  | 'Russia'
19
- | 'Central Europe';
19
+ | 'Central Europe'
20
+ | 'Baden-Württemberg'
21
+ | 'Northern Europe'
22
+ | 'Korea';
20
23
  // | 'Australia'
21
- // | 'Baden-Württemberg'
22
24
  // | 'Japan'
23
- // | 'Korea'
24
- // | 'Northern Europe'
25
25
  // | 'South Africa'
26
26
  // | 'UK & Ireland'
27
27
  export type Variant = 'original' | 'recharged';
@@ -129,6 +129,22 @@ export interface GameState {
129
129
  oilResupply?: number[][];
130
130
  garbageResupply?: number[][];
131
131
  uraniumResupply?: number[][];
132
+ // Korea: parallel North-side markets and resupply tables. The primary
133
+ // `*Market`/`*Resupply` fields above act as the South side. The supply pools
134
+ // (`coalSupply` etc.) are SHARED — used cubes return there and both sides
135
+ // restock from the same pool, with North restocking first.
136
+ // North has no uranium track (nuclear plants are banned on the North side).
137
+ coalMarketNorth?: number;
138
+ oilMarketNorth?: number;
139
+ garbageMarketNorth?: number;
140
+ coalResupplyNorth?: number[][];
141
+ oilResupplyNorth?: number[][];
142
+ garbageResupplyNorth?: number[][];
143
+ // Korea: per-side price arrays. Each side's market has different slot counts
144
+ // per price space, so the price arrays differ. (No uranium on the North side.)
145
+ coalPricesNorth?: number[];
146
+ oilPricesNorth?: number[];
147
+ garbagePricesNorth?: number[];
132
148
  coalPrices?: number[];
133
149
  oilPrices?: number[];
134
150
  garbagePrices?: number[];
@@ -137,6 +153,9 @@ export interface GameState {
137
153
  futureMarket: PowerPlant[];
138
154
  chosenPowerPlant: PowerPlant | undefined;
139
155
  chosenResource?: ResourceType | undefined; // Used for India map, where only one resource can be bought at a time.
156
+ // Korea: locked to the side a player bought their first resource from this turn.
157
+ // All subsequent buys this turn must be from the same side. Cleared when the player passes.
158
+ chosenSide?: 'north' | 'south';
140
159
  currentBid: number | undefined;
141
160
  auctioningPlayer: number | undefined;
142
161
  step: number;
@@ -151,6 +170,7 @@ export interface GameState {
151
170
  citiesToEndGame: number;
152
171
  citiesBuiltInCurrentRound?: number; // In India, if the players build too many cities in a single round, a power outage will occur.
153
172
  resourceResupply: string[];
173
+ resourceResupplyNorth?: string[]; // Korea: per-step North resupply, parallel to resourceResupply (no uranium).
154
174
  paymentTable: number[];
155
175
  minimunBid: number;
156
176
  plantDiscountActive: boolean;
package/src/maps/korea.ts CHANGED
@@ -1,3 +1,4 @@
1
+ // by John and Cici
1
2
  import { GameMap } from './../maps';
2
3
 
3
4
  export enum Regions {
@@ -182,4 +183,112 @@ export const map: GameMap = {
182
183
  { nodes: [Cities.Seongjin, Cities.Hamheung], cost: 17 },
183
184
  { nodes: [Cities.Hamheung, Cities.Hyesan], cost: 23 },
184
185
  ],
186
+ layout: 'Portrait',
187
+ adjustRatio: [0.22, 0.22],
188
+ mapPosition: [80, 30],
189
+ // Taller than the default Portrait viewBox to make room for Korea's stacked
190
+ // dual resource markets (North above South).
191
+ viewBox: [1480, 1180],
192
+ supplyPosition: [675, 1040],
193
+ // South-side resource market.
194
+ // Indexed [resource][playerCount-2][step-1].
195
+ // Verified from the Korea Recharged rulebook resource table (S rows).
196
+ resupply: [
197
+ // Coal (S)
198
+ [
199
+ [2, 2, 2], // 2P
200
+ [2, 3, 2], // 3P
201
+ [3, 3, 2], // 4P
202
+ [3, 4, 3], // 5P
203
+ [4, 5, 3], // 6P
204
+ ],
205
+ // Oil (S)
206
+ [
207
+ [1, 1, 3],
208
+ [1, 2, 3],
209
+ [2, 3, 3],
210
+ [3, 3, 4],
211
+ [3, 4, 4],
212
+ ],
213
+ // Garbage (S)
214
+ [
215
+ [1, 1, 2],
216
+ [1, 1, 2],
217
+ [1, 2, 2],
218
+ [2, 2, 3],
219
+ [2, 3, 3],
220
+ ],
221
+ // Uranium (S only — North bans nuclear resupply)
222
+ [
223
+ [1, 1, 1],
224
+ [1, 1, 1],
225
+ [1, 2, 2],
226
+ [2, 3, 2],
227
+ [2, 3, 3],
228
+ ],
229
+ ],
230
+ // North-side resource market. Three resources only (no uranium row).
231
+ // Verified from the Korea Recharged rulebook resource table (N rows).
232
+ resupplyNorth: [
233
+ // Coal (N)
234
+ [
235
+ [1, 2, 1],
236
+ [2, 2, 1],
237
+ [2, 3, 2],
238
+ [2, 3, 2],
239
+ [3, 4, 3],
240
+ ],
241
+ // Oil (N)
242
+ [
243
+ [1, 1, 1],
244
+ [1, 1, 1],
245
+ [1, 1, 2],
246
+ [1, 2, 2],
247
+ [2, 2, 3],
248
+ ],
249
+ // Garbage (N)
250
+ [
251
+ [0, 1, 1],
252
+ [0, 1, 1],
253
+ [1, 1, 2],
254
+ [1, 1, 2],
255
+ [1, 2, 3],
256
+ ],
257
+ ],
258
+ // Korea has custom slot counts per price space, different per side.
259
+ // Total resources (across both markets + supply) match standard Power Grid:
260
+ // 24 coal, 24 oil, 24 garbage, 12 uranium.
261
+ //
262
+ // North slots per price [1..8]:
263
+ // coal [2,2,2,2,1,1,1,1] = 12 total
264
+ // oil [1,1,1,1,1,1,1,1] = 8 total
265
+ // garbage [1,1,1,1,1,1,1,1] = 8 total
266
+ //
267
+ // South slots per price [1..8] (uranium prices [1..8,10,12,14,16]):
268
+ // coal [1,1,1,1,2,2,2,2] = 12 total
269
+ // oil [2,2,2,2,2,2,2,2] = 16 total
270
+ // garbage [2,2,2,2,2,2,2,2] = 16 total
271
+ // uranium [1,1,1,1,1,1,1,1,1,1,1,1] = 12 total
272
+ //
273
+ // Initial market fill per rulebook:
274
+ // North: coal 1–8 (12), oil 3–8 (6), garbage 7–8 (2).
275
+ // South: coal 1–8 (12), oil 3–8 (12), garbage 7–8 (4), uranium 14–16 (2).
276
+ startingResources: [12, 12, 4, 2], // South initial market: coal, oil, garbage, uranium
277
+ startingResourcesNorth: [12, 6, 2], // North initial market: coal, oil, garbage
278
+ // Total cubes in the game across BOTH markets + shared supply.
279
+ // Used cubes return to the shared supply; on bureaucracy, North restocks
280
+ // first, then South from whatever remains.
281
+ startingSupply: [24, 24, 24, 12],
282
+ coalPrices: [1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8],
283
+ oilPrices: [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8],
284
+ garbagePrices: [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8],
285
+ uraniumPrices: [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16],
286
+ coalPricesNorth: [1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8],
287
+ oilPricesNorth: [1, 2, 3, 4, 5, 6, 7, 8],
288
+ garbagePricesNorth: [1, 2, 3, 4, 5, 6, 7, 8],
289
+ mapSpecificRules:
290
+ 'Korea has two separate resource markets: North and South.\n' +
291
+ 'On your buying turn, choose one side — all resources you buy that turn must come from that side. The next turn you may choose the other side.\n' +
292
+ 'The North market has no uranium track; uranium is only available from the South.\n' +
293
+ 'Both markets share a single supply pool. During bureaucracy, North restocks first, then South from whatever remains.',
185
294
  };