powergrid-engine 1.9.7

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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.js +17 -0
  5. package/dist/src/available-moves.d.ts +24 -0
  6. package/dist/src/available-moves.js +363 -0
  7. package/dist/src/engine.d.ts +20 -0
  8. package/dist/src/engine.js +1937 -0
  9. package/dist/src/gamestate.d.ts +135 -0
  10. package/dist/src/gamestate.js +30 -0
  11. package/dist/src/log.d.ts +14 -0
  12. package/dist/src/log.js +2 -0
  13. package/dist/src/maps/america.d.ts +55 -0
  14. package/dist/src/maps/america.js +411 -0
  15. package/dist/src/maps/australia.d.ts +46 -0
  16. package/dist/src/maps/australia.js +138 -0
  17. package/dist/src/maps/badenwurttemberg.d.ts +46 -0
  18. package/dist/src/maps/badenwurttemberg.js +163 -0
  19. package/dist/src/maps/benelux.d.ts +46 -0
  20. package/dist/src/maps/benelux.js +210 -0
  21. package/dist/src/maps/brazil.d.ts +54 -0
  22. package/dist/src/maps/brazil.js +292 -0
  23. package/dist/src/maps/centraleurope.d.ts +54 -0
  24. package/dist/src/maps/centraleurope.js +236 -0
  25. package/dist/src/maps/china.d.ts +54 -0
  26. package/dist/src/maps/china.js +262 -0
  27. package/dist/src/maps/france.d.ts +54 -0
  28. package/dist/src/maps/france.js +290 -0
  29. package/dist/src/maps/germany.d.ts +57 -0
  30. package/dist/src/maps/germany.js +328 -0
  31. package/dist/src/maps/indian.d.ts +54 -0
  32. package/dist/src/maps/indian.js +283 -0
  33. package/dist/src/maps/italy.d.ts +54 -0
  34. package/dist/src/maps/italy.js +190 -0
  35. package/dist/src/maps/japan.d.ts +46 -0
  36. package/dist/src/maps/japan.js +144 -0
  37. package/dist/src/maps/korea.d.ts +54 -0
  38. package/dist/src/maps/korea.js +186 -0
  39. package/dist/src/maps/middleeast.d.ts +54 -0
  40. package/dist/src/maps/middleeast.js +225 -0
  41. package/dist/src/maps/northerneurope.d.ts +54 -0
  42. package/dist/src/maps/northerneurope.js +197 -0
  43. package/dist/src/maps/quebec.d.ts +54 -0
  44. package/dist/src/maps/quebec.js +283 -0
  45. package/dist/src/maps/russia.d.ts +54 -0
  46. package/dist/src/maps/russia.js +286 -0
  47. package/dist/src/maps/southafrica.d.ts +46 -0
  48. package/dist/src/maps/southafrica.js +152 -0
  49. package/dist/src/maps/spainportugal.d.ts +54 -0
  50. package/dist/src/maps/spainportugal.js +289 -0
  51. package/dist/src/maps/ukireland.d.ts +52 -0
  52. package/dist/src/maps/ukireland.js +176 -0
  53. package/dist/src/maps.d.ts +50 -0
  54. package/dist/src/maps.js +61 -0
  55. package/dist/src/move.d.ts +63 -0
  56. package/dist/src/move.js +15 -0
  57. package/dist/src/powerPlants.d.ts +4 -0
  58. package/dist/src/powerPlants.js +60 -0
  59. package/dist/src/prices.d.ts +7 -0
  60. package/dist/src/prices.js +10 -0
  61. package/dist/src/randomizeMap.d.ts +3 -0
  62. package/dist/src/randomizeMap.js +244 -0
  63. package/dist/src/utils.d.ts +2 -0
  64. package/dist/src/utils.js +24 -0
  65. package/dist/wrapper.d.ts +30 -0
  66. package/dist/wrapper.js +127 -0
  67. package/index.ts +6 -0
  68. package/package.json +51 -0
  69. package/src/available-moves.ts +450 -0
  70. package/src/engine.spec.ts +163 -0
  71. package/src/engine.ts +2270 -0
  72. package/src/fixtures/GermanyRecharged.json +6627 -0
  73. package/src/fixtures/USAOriginal.json +5216 -0
  74. package/src/fixtures/supply.json +5792 -0
  75. package/src/fixtures/undo.json +4102 -0
  76. package/src/gamestate.ts +164 -0
  77. package/src/log.ts +17 -0
  78. package/src/maps/america.ts +411 -0
  79. package/src/maps/australia.ts +137 -0
  80. package/src/maps/badenwurttemberg.ts +162 -0
  81. package/src/maps/benelux.ts +210 -0
  82. package/src/maps/brazil.ts +306 -0
  83. package/src/maps/centraleurope.ts +235 -0
  84. package/src/maps/china.ts +268 -0
  85. package/src/maps/france.ts +295 -0
  86. package/src/maps/germany.ts +328 -0
  87. package/src/maps/indian.ts +289 -0
  88. package/src/maps/italy.ts +189 -0
  89. package/src/maps/japan.ts +143 -0
  90. package/src/maps/korea.ts +185 -0
  91. package/src/maps/middleeast.ts +225 -0
  92. package/src/maps/northerneurope.ts +196 -0
  93. package/src/maps/quebec.ts +304 -0
  94. package/src/maps/russia.ts +292 -0
  95. package/src/maps/southafrica.ts +151 -0
  96. package/src/maps/spainportugal.ts +295 -0
  97. package/src/maps/ukireland.ts +175 -0
  98. package/src/maps.ts +123 -0
  99. package/src/move.ts +83 -0
  100. package/src/powerPlants.ts +59 -0
  101. package/src/prices.ts +10 -0
  102. package/src/randomizeMap.ts +288 -0
  103. package/src/rankings.spec.ts +18 -0
  104. package/src/utils.spec.ts +13 -0
  105. package/src/utils.ts +23 -0
  106. package/tsconfig.json +17 -0
  107. package/wrapper.ts +126 -0
@@ -0,0 +1,1937 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.playersSortedByScore = exports.getPowerPlant = exports.reconstructState = exports.scores = exports.ended = exports.moveAI = exports.move = exports.currentPlayers = exports.stripSecret = exports.setup = exports.defaultSetupDeck = exports.playerColors = void 0;
7
+ const assert_1 = __importDefault(require("assert"));
8
+ const lodash_1 = require("lodash");
9
+ const seedrandom_1 = __importDefault(require("seedrandom"));
10
+ const available_moves_1 = require("./available-moves");
11
+ const gamestate_1 = require("./gamestate");
12
+ const maps_1 = require("./maps");
13
+ const move_1 = require("./move");
14
+ const powerPlants_1 = require("./powerPlants");
15
+ const prices_1 = __importDefault(require("./prices"));
16
+ const randomizeMap_1 = require("./randomizeMap");
17
+ const utils_1 = require("./utils");
18
+ exports.playerColors = ['limegreen', 'mediumorchid', 'red', 'dodgerblue', 'yellow', 'brown'];
19
+ const citiesToStep2 = [10, 7, 7, 7, 6];
20
+ const citiesToEndGame = [21, 17, 17, 15, 14];
21
+ const cityIncome = [10, 22, 33, 44, 54, 64, 73, 82, 90, 98, 105, 112, 118, 124, 129, 134, 138, 142, 145, 148, 150, 150];
22
+ const regionsInPlay = [3, 3, 4, 5, 5];
23
+ function defaultSetupDeck(numPlayers, variant, rng, useNewRechargedSetup) {
24
+ let actualMarket;
25
+ let futureMarket;
26
+ let powerPlantsDeck = lodash_1.cloneDeep(powerPlants_1.powerPlants);
27
+ if (variant == 'original') {
28
+ powerPlantsDeck = powerPlantsDeck.slice(8);
29
+ const powerPlant13 = powerPlantsDeck.splice(2, 1)[0];
30
+ const step3 = powerPlantsDeck.pop();
31
+ powerPlantsDeck = utils_1.shuffle(powerPlantsDeck, rng() + '');
32
+ if (numPlayers == 2 || numPlayers == 3) {
33
+ powerPlantsDeck = powerPlantsDeck.slice(8);
34
+ }
35
+ else if (numPlayers == 4) {
36
+ powerPlantsDeck = powerPlantsDeck.slice(4);
37
+ }
38
+ powerPlantsDeck.unshift(powerPlant13);
39
+ powerPlantsDeck.push(step3);
40
+ actualMarket = [getPowerPlant(3), getPowerPlant(4), getPowerPlant(5), getPowerPlant(6)];
41
+ futureMarket = [getPowerPlant(7), getPowerPlant(8), getPowerPlant(9), getPowerPlant(10)];
42
+ }
43
+ else {
44
+ let initialPowerPlants = utils_1.shuffle(powerPlantsDeck.splice(0, 13), rng() + '');
45
+ let initialPlantMarket = initialPowerPlants.splice(0, 8);
46
+ initialPlantMarket = initialPlantMarket.sort((a, b) => a.number - b.number);
47
+ actualMarket = initialPlantMarket.slice(0, 4);
48
+ futureMarket = initialPlantMarket.slice(4);
49
+ const first = initialPowerPlants.shift();
50
+ const step3 = powerPlantsDeck.pop();
51
+ powerPlantsDeck = utils_1.shuffle(powerPlantsDeck, rng() + '');
52
+ if (numPlayers == 2 || numPlayers == 3) {
53
+ initialPowerPlants = initialPowerPlants.slice(2);
54
+ powerPlantsDeck = utils_1.shuffle(powerPlantsDeck.slice(6).concat(initialPowerPlants), rng() + '');
55
+ }
56
+ else if (numPlayers == 4) {
57
+ initialPowerPlants = initialPowerPlants.slice(1);
58
+ powerPlantsDeck = utils_1.shuffle(powerPlantsDeck.slice(3).concat(initialPowerPlants), rng() + '');
59
+ }
60
+ else if (useNewRechargedSetup) {
61
+ // TODO: This flag exists solely to make old tests pass. We should eventually
62
+ // fix the test and remove the flag.
63
+ powerPlantsDeck = utils_1.shuffle(powerPlantsDeck.concat(initialPowerPlants), rng() + '');
64
+ }
65
+ powerPlantsDeck.unshift(first);
66
+ powerPlantsDeck.push(step3);
67
+ }
68
+ return { actualMarket, futureMarket, powerPlantsDeck };
69
+ }
70
+ exports.defaultSetupDeck = defaultSetupDeck;
71
+ function setup(numPlayers, { fastBid = false, map = 'USA', variant = 'original', showMoney = false, useNewRechargedSetup = true, trackTotalSpent = true, randomizeMap = false, }, seed, forceDeck, forceMap) {
72
+ var _a, _b, _c, _d;
73
+ seed = seed !== null && seed !== void 0 ? seed : Math.random().toString();
74
+ const rng = seedrandom_1.default(seed);
75
+ const chosenMap = lodash_1.cloneDeep(variant == 'original' ? maps_1.maps.find((m) => m.name == map) : maps_1.mapsRecharged.find((m) => m.name == map));
76
+ let actualMarket;
77
+ let futureMarket;
78
+ let powerPlantsDeck;
79
+ if (forceDeck) {
80
+ powerPlantsDeck = forceDeck;
81
+ actualMarket = powerPlantsDeck.splice(0, 4);
82
+ futureMarket = powerPlantsDeck.splice(0, 4);
83
+ }
84
+ else {
85
+ if (chosenMap.setupDeck) {
86
+ ({ actualMarket, futureMarket, powerPlantsDeck } = chosenMap.setupDeck(numPlayers, variant, rng));
87
+ }
88
+ else {
89
+ ({ actualMarket, futureMarket, powerPlantsDeck } = defaultSetupDeck(numPlayers, variant, rng, useNewRechargedSetup));
90
+ }
91
+ }
92
+ const players = lodash_1.range(numPlayers).map((id) => ({
93
+ id,
94
+ powerPlants: [],
95
+ coalCapacity: 0,
96
+ coalLeft: 0,
97
+ oilCapacity: 0,
98
+ oilLeft: 0,
99
+ garbageCapacity: 0,
100
+ garbageLeft: 0,
101
+ uraniumCapacity: 0,
102
+ uraniumLeft: 0,
103
+ hybridCapacity: 0,
104
+ hybridLeft: 0,
105
+ money: 50,
106
+ housesLeft: 22,
107
+ cities: [],
108
+ powerPlantsNotUsed: [],
109
+ availableMoves: null,
110
+ lastMove: null,
111
+ isDropped: false,
112
+ isAI: false,
113
+ bid: 0,
114
+ passed: false,
115
+ skipAuction: false,
116
+ citiesPowered: 0,
117
+ resourcesUsed: [],
118
+ totalIncome: 0,
119
+ totalSpentCities: 0,
120
+ totalSpentConnections: 0,
121
+ totalSpentPlants: 0,
122
+ totalSpentResources: 0,
123
+ }));
124
+ const p = players.length - 2;
125
+ const playerOrder = lodash_1.range(numPlayers);
126
+ const startingPlayer = playerOrder[0];
127
+ let coalResupply;
128
+ let oilResupply;
129
+ let garbageResupply;
130
+ let uraniumResupply;
131
+ if (chosenMap.resupply) {
132
+ coalResupply = chosenMap.resupply[0];
133
+ oilResupply = chosenMap.resupply[1];
134
+ garbageResupply = chosenMap.resupply[2];
135
+ uraniumResupply = chosenMap.resupply[3];
136
+ }
137
+ else {
138
+ coalResupply = [
139
+ [3, 4, 3],
140
+ [4, 5, 3],
141
+ [5, 6, 4],
142
+ [5, 7, 5],
143
+ [7, 9, 6],
144
+ ];
145
+ oilResupply = [
146
+ [2, 2, 4],
147
+ [2, 3, 4],
148
+ [3, 4, 5],
149
+ [4, 5, 6],
150
+ [5, 6, 7],
151
+ ];
152
+ garbageResupply = [
153
+ [1, 2, 3],
154
+ [1, 2, 3],
155
+ [2, 3, 4],
156
+ [3, 3, 5],
157
+ [3, 5, 6],
158
+ ];
159
+ uraniumResupply = [
160
+ [1, 1, 1],
161
+ [1, 1, 1],
162
+ [1, 2, 2],
163
+ [2, 3, 2],
164
+ [2, 3, 3],
165
+ ];
166
+ }
167
+ if (chosenMap.layout == 'Portrait' || randomizeMap) {
168
+ const isUsaRecharged = chosenMap.name === 'USA' && variant === 'recharged';
169
+ chosenMap.viewBox = chosenMap.viewBox || [1480, 1060];
170
+ chosenMap.adjustRatio = chosenMap.adjustRatio || [1, 1];
171
+ chosenMap.playerOrderPosition = chosenMap.playerOrderPosition || [1160, 160];
172
+ chosenMap.cityCountPosition = chosenMap.cityCountPosition || [0, 0];
173
+ chosenMap.powerPlantMarketPosition = chosenMap.powerPlantMarketPosition || [745, 0];
174
+ chosenMap.actualMarketWidth = chosenMap.actualMarketWidth || 430;
175
+ chosenMap.mapPosition = chosenMap.mapPosition || [0, 0];
176
+ chosenMap.buttonsPosition = chosenMap.buttonsPosition || [1305, 0];
177
+ chosenMap.playerBoardsPosition = chosenMap.playerBoardsPosition || [1105, 240];
178
+ chosenMap.roundInfoPosition = chosenMap.roundInfoPosition || [20, 920];
179
+ chosenMap.supplyPosition = chosenMap.supplyPosition || [isUsaRecharged ? 480 : 675, 920];
180
+ }
181
+ else {
182
+ chosenMap.viewBox = chosenMap.viewBox || [1465, 860];
183
+ chosenMap.adjustRatio = chosenMap.adjustRatio || [1, 1];
184
+ chosenMap.playerOrderPosition = chosenMap.playerOrderPosition || [1160, 140];
185
+ chosenMap.cityCountPosition = chosenMap.cityCountPosition || [0, 0];
186
+ chosenMap.powerPlantMarketPosition = chosenMap.powerPlantMarketPosition || [745, 0];
187
+ chosenMap.actualMarketWidth = chosenMap.actualMarketWidth || 430;
188
+ chosenMap.mapPosition = chosenMap.mapPosition || [-10, 0];
189
+ chosenMap.buttonsPosition = chosenMap.buttonsPosition || [1305, 0];
190
+ chosenMap.playerBoardsPosition = chosenMap.playerBoardsPosition || [1105, 200];
191
+ chosenMap.roundInfoPosition = chosenMap.roundInfoPosition || [20, 590];
192
+ chosenMap.supplyPosition = chosenMap.supplyPosition || [0, 720];
193
+ }
194
+ let finalMap;
195
+ if (randomizeMap) {
196
+ finalMap = randomizeMap_1.createRandomizedMap(chosenMap, regionsInPlay[p], rng);
197
+ }
198
+ else {
199
+ chosenMap.cities = chosenMap.cities.map((city) => ({
200
+ ...city,
201
+ x: city.x * chosenMap.adjustRatio[0],
202
+ y: city.y * chosenMap.adjustRatio[1],
203
+ }));
204
+ const regions = chosenMap.cities
205
+ .filter((c, i) => chosenMap.cities.findIndex((cc) => cc.region == c.region) == i)
206
+ .map((c) => c.region);
207
+ const connections = chosenMap.connections.map((con) => con.nodes.map((n) => chosenMap.cities.find((city) => city.name == n).region));
208
+ const regionConnections = regions.map((region) => regions.filter((area2) => region != area2 && connections.some((con) => con.includes(region) && con.includes(area2))));
209
+ const playRegions = new Set();
210
+ while (playRegions.size != Math.min(regionsInPlay[p], regions.length)) {
211
+ const region = regions[Math.floor(rng() * regions.length)];
212
+ if (playRegions.size == 0 ||
213
+ regionConnections[regions.indexOf(region)].some((con) => playRegions.has(con))) {
214
+ playRegions.add(region);
215
+ // Avoid italy Red Green Blue
216
+ if (chosenMap.name === 'Italy') {
217
+ if (playRegions.has('red') && playRegions.has('green') && playRegions.has('blue')) {
218
+ playRegions.clear();
219
+ }
220
+ }
221
+ }
222
+ }
223
+ const filteredMap = lodash_1.cloneDeep(chosenMap);
224
+ filteredMap.cities = filteredMap.cities.filter((city) => playRegions.has(city.region));
225
+ filteredMap.connections = filteredMap.connections.filter((con) => con.nodes
226
+ .map((n) => chosenMap.cities.find((city) => city.name == n).region)
227
+ .every((r) => playRegions.has(r)));
228
+ finalMap = filteredMap;
229
+ }
230
+ const coalMarket = chosenMap.startingResources ? chosenMap.startingResources[0] : 24;
231
+ const oilMarket = chosenMap.startingResources ? chosenMap.startingResources[1] : 18;
232
+ const garbageMarket = chosenMap.startingResources ? chosenMap.startingResources[2] : variant == 'original' ? 6 : 9;
233
+ const uraniumMarket = chosenMap.startingResources ? chosenMap.startingResources[3] : 2;
234
+ const totalCoal = chosenMap.startingSupply ? chosenMap.startingSupply[0] : 24;
235
+ const totalOil = chosenMap.startingSupply ? chosenMap.startingSupply[1] : 24;
236
+ const totalGarbage = chosenMap.startingSupply ? chosenMap.startingSupply[2] : 24;
237
+ const totalUranium = chosenMap.startingSupply ? chosenMap.startingSupply[3] : 12;
238
+ const coalSupply = totalCoal - coalMarket;
239
+ const oilSupply = totalOil - oilMarket;
240
+ const garbageSupply = totalGarbage - garbageMarket;
241
+ const uraniumSupply = totalUranium - uraniumMarket;
242
+ const coalPrices = lodash_1.cloneDeep((_a = chosenMap.coalPrices) !== null && _a !== void 0 ? _a : prices_1.default.coal);
243
+ const oilPrices = lodash_1.cloneDeep((_b = chosenMap.oilPrices) !== null && _b !== void 0 ? _b : prices_1.default.oil);
244
+ const garbagePrices = lodash_1.cloneDeep((_c = chosenMap.garbagePrices) !== null && _c !== void 0 ? _c : prices_1.default.garbage);
245
+ const uraniumPrices = lodash_1.cloneDeep((_d = chosenMap.uraniumPrices) !== null && _d !== void 0 ? _d : prices_1.default.uranium);
246
+ const G = {
247
+ map: forceMap || finalMap,
248
+ players,
249
+ playerOrder,
250
+ currentPlayers: [startingPlayer],
251
+ powerPlantsDeck,
252
+ coalSupply,
253
+ oilSupply,
254
+ garbageSupply,
255
+ uraniumSupply,
256
+ coalResupply,
257
+ oilResupply,
258
+ garbageResupply,
259
+ uraniumResupply,
260
+ coalMarket,
261
+ oilMarket,
262
+ garbageMarket,
263
+ uraniumMarket,
264
+ coalPrices,
265
+ oilPrices,
266
+ garbagePrices,
267
+ uraniumPrices,
268
+ actualMarket,
269
+ futureMarket,
270
+ chosenPowerPlant: undefined,
271
+ currentBid: undefined,
272
+ highestBidders: [],
273
+ auctioningPlayer: undefined,
274
+ step: 1,
275
+ phase: gamestate_1.Phase.Auction,
276
+ options: { fastBid, map, variant, showMoney, useNewRechargedSetup, trackTotalSpent },
277
+ log: [],
278
+ hiddenLog: [],
279
+ seed,
280
+ round: 1,
281
+ auctionSkips: 0,
282
+ citiesToStep2: citiesToStep2[numPlayers - 2],
283
+ citiesToEndGame: citiesToEndGame[numPlayers - 2],
284
+ resourceResupply: [
285
+ `[${coalResupply[p][0]}, ${oilResupply[p][0]}, ${garbageResupply[p][0]}, ${uraniumResupply[p][0]}]`,
286
+ `[${coalResupply[p][1]}, ${oilResupply[p][1]}, ${garbageResupply[p][1]}, ${uraniumResupply[p][1]}]`,
287
+ `[${coalResupply[p][2]}, ${oilResupply[p][2]}, ${garbageResupply[p][2]}, ${uraniumResupply[p][2]}]`,
288
+ ],
289
+ paymentTable: cityIncome,
290
+ variant,
291
+ minimunBid: 0,
292
+ plantDiscountActive: variant == 'recharged' && (forceMap || finalMap).name != 'China' && (forceMap || finalMap).name != 'Russia',
293
+ discardSmallestPlant: false,
294
+ cardsLeft: powerPlantsDeck.length,
295
+ nextCardWeak: variant == 'recharged',
296
+ card39Bought: false,
297
+ knownPowerPlantDeck: actualMarket.concat(futureMarket),
298
+ knownPowerPlantDeckStep3: [],
299
+ powerPlantDeckAfterStep3: undefined,
300
+ };
301
+ G.log.push({ type: 'event', event: 'Game Start!' });
302
+ if (G.map.name == 'Middle East') {
303
+ removePlantsForMiddleEastStep1(G);
304
+ }
305
+ G.players[startingPlayer].availableMoves = available_moves_1.availableMoves(G, G.players[startingPlayer]);
306
+ return G;
307
+ }
308
+ exports.setup = setup;
309
+ function stripSecret(G, player) {
310
+ return {
311
+ ...G,
312
+ seed: 'secret',
313
+ hiddenLog: [],
314
+ powerPlantsDeck: [],
315
+ players: G.players.map((pl, i) => {
316
+ if (player === i) {
317
+ return pl;
318
+ }
319
+ else {
320
+ return {
321
+ ...pl,
322
+ availableMoves: pl.availableMoves ? {} : null,
323
+ money: ended(G) || G.options.showMoney ? pl.money : -1,
324
+ bid: G.options.fastBid ? 0 : pl.bid,
325
+ };
326
+ }
327
+ }),
328
+ log: G.log,
329
+ };
330
+ }
331
+ exports.stripSecret = stripSecret;
332
+ function currentPlayers(G) {
333
+ return G.currentPlayers;
334
+ }
335
+ exports.currentPlayers = currentPlayers;
336
+ function move(G, move, playerNumber, isUndo = false) {
337
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
338
+ const player = G.players[playerNumber];
339
+ const available = (_a = player.availableMoves) === null || _a === void 0 ? void 0 : _a[move.name];
340
+ updateGameState(G);
341
+ assert_1.default(G.currentPlayers.includes(playerNumber), 'It is not your turn!');
342
+ assert_1.default(available, 'You are not allowed to run the command ' + move.name);
343
+ // Fix for issue 8: can't undo because of a move (discaring the pp you just bought) that is now invalid
344
+ if (!isUndo ||
345
+ move.name != move_1.MoveName.DiscardPowerPlant ||
346
+ player.powerPlants[player.powerPlants.length - 1].number != move.data) {
347
+ assert_1.default(available.some((x) => lodash_1.isEqual(x, move.data)), 'Wrong argument for the command ' + move.name);
348
+ }
349
+ switch (move.name) {
350
+ case move_1.MoveName.ChoosePowerPlant: {
351
+ utils_1.asserts(move);
352
+ G.chosenPowerPlant = getPowerPlant(move.data, G.map.name);
353
+ G.auctioningPlayer = player.id;
354
+ if (move.data == 39) {
355
+ G.card39Bought = true;
356
+ }
357
+ if (G.options.variant == 'recharged' &&
358
+ G.plantDiscountActive &&
359
+ G.chosenPowerPlant.number == G.actualMarket[0].number) {
360
+ G.minimunBid = 1;
361
+ G.plantDiscountActive = false;
362
+ move.usedPlantDiscount = true;
363
+ }
364
+ else {
365
+ G.minimunBid = move.data;
366
+ }
367
+ const notPassed = G.players.filter((p) => !p.skipAuction && !p.isDropped);
368
+ if (notPassed.length == 1) {
369
+ G.log.push({
370
+ type: 'move',
371
+ player: playerNumber,
372
+ move,
373
+ simple: `${player.name} chooses Power Plant ${move.data}.`,
374
+ pretty: `${playerNameHTML(player)} chooses Power Plant <b>${move.data}</b>.`,
375
+ });
376
+ const winningPlayer = notPassed[0];
377
+ endAuction(G, winningPlayer, G.minimunBid);
378
+ if (G.round == 1) {
379
+ setPlayerOrder(G);
380
+ }
381
+ if (winningPlayer.powerPlants.length <= 3 ||
382
+ (G.players.length == 2 && winningPlayer.powerPlants.length == 4)) {
383
+ if (G.map.name != 'China' || G.step == 3) {
384
+ addPowerPlant(G);
385
+ }
386
+ toResourcesPhase(G);
387
+ }
388
+ }
389
+ else {
390
+ G.log.push({
391
+ type: 'move',
392
+ player: playerNumber,
393
+ move,
394
+ simple: `${player.name} chooses Power Plant ${move.data} to initiate an auction.`,
395
+ pretty: `${playerNameHTML(player)} chooses Power Plant <b>${move.data}</b> to initiate an auction.`,
396
+ });
397
+ if (G.options.fastBid) {
398
+ G.currentPlayers = G.playerOrder.filter((p) => !G.players[p].skipAuction && !G.players[p].isDropped);
399
+ }
400
+ }
401
+ break;
402
+ }
403
+ case move_1.MoveName.Bid: {
404
+ utils_1.asserts(move);
405
+ if (G.options.fastBid) {
406
+ G.hiddenLog.push({
407
+ type: 'move',
408
+ player: playerNumber,
409
+ move,
410
+ simple: `${player.name} bids $${move.data}.`,
411
+ pretty: `${playerNameHTML(player)} bids <span style="color: green">$${move.data}</span>.`,
412
+ });
413
+ fastAuction(G, player, move.data);
414
+ }
415
+ else {
416
+ G.currentBid = player.bid = move.data;
417
+ G.log.push({
418
+ type: 'move',
419
+ player: playerNumber,
420
+ move,
421
+ simple: `${player.name} bids $${move.data}.`,
422
+ pretty: `${playerNameHTML(player)} bids <span style="color: green">$${move.data}</span>.`,
423
+ });
424
+ nextPlayerClockwise(G);
425
+ }
426
+ break;
427
+ }
428
+ case move_1.MoveName.Pass: {
429
+ utils_1.asserts(move);
430
+ if (!G.options.fastBid || G.phase != gamestate_1.Phase.Auction || !G.chosenPowerPlant) {
431
+ G.log.push({
432
+ type: 'move',
433
+ player: playerNumber,
434
+ move,
435
+ simple: `${player.name} passes.`,
436
+ pretty: `${playerNameHTML(player)} passes.`,
437
+ });
438
+ }
439
+ switch (G.phase) {
440
+ case gamestate_1.Phase.Auction: {
441
+ if (G.chosenPowerPlant == undefined) {
442
+ player.skipAuction = true;
443
+ G.auctionSkips++;
444
+ if (G.auctionSkips == 1 && G.map.name == 'Russia' && ((_b = G.actualMarket) === null || _b === void 0 ? void 0 : _b.length) > 0) {
445
+ G.log.push({
446
+ type: 'event',
447
+ event: `First pass, removing lowest numbered Power Plant (${G.actualMarket[0].number}).`,
448
+ });
449
+ G.actualMarket.shift();
450
+ addPowerPlant(G);
451
+ }
452
+ if (G.players.some((p) => !p.skipAuction && !p.isDropped)) {
453
+ nextPlayerAuction(G);
454
+ }
455
+ else {
456
+ if (G.auctionSkips == G.players.length &&
457
+ G.options.variant == 'original' &&
458
+ G.map.name != 'China' &&
459
+ G.map.name != 'Russia') {
460
+ G.log.push({
461
+ type: 'event',
462
+ event: `Everyone passed, removing lowest numbered Power Plant (${G.actualMarket[0].number}).`,
463
+ });
464
+ G.actualMarket.shift();
465
+ addPowerPlant(G);
466
+ }
467
+ toResourcesPhase(G);
468
+ }
469
+ }
470
+ else {
471
+ if (G.options.fastBid) {
472
+ G.hiddenLog.push({
473
+ type: 'move',
474
+ player: playerNumber,
475
+ move,
476
+ simple: `${player.name} passes.`,
477
+ pretty: `${playerNameHTML(player)} passes.`,
478
+ });
479
+ fastAuction(G, player, 0);
480
+ }
481
+ else {
482
+ player.passed = true;
483
+ const notPassed = G.players.filter((p) => !p.passed && !p.skipAuction && !p.isDropped);
484
+ if (notPassed.length == 1) {
485
+ const winningPlayer = notPassed[0];
486
+ endAuction(G, winningPlayer, winningPlayer.bid);
487
+ if ((winningPlayer.powerPlants.length > 4 ||
488
+ (G.players.length > 2 && winningPlayer.powerPlants.length > 3)) &&
489
+ !winningPlayer.isDropped) {
490
+ setCurrentPlayer(G, winningPlayer.id);
491
+ }
492
+ else {
493
+ if (G.map.name != 'China' || G.step == 3) {
494
+ addPowerPlant(G);
495
+ }
496
+ if (G.players.some((p) => !p.skipAuction && !p.isDropped)) {
497
+ G.players.forEach((p) => {
498
+ p.bid = 0;
499
+ p.passed = p.isDropped;
500
+ });
501
+ nextPlayerAuction(G, true);
502
+ }
503
+ else {
504
+ toResourcesPhase(G);
505
+ }
506
+ }
507
+ }
508
+ else {
509
+ nextPlayerClockwise(G);
510
+ }
511
+ }
512
+ }
513
+ break;
514
+ }
515
+ case gamestate_1.Phase.Resources: {
516
+ if (G.map.name == 'India') {
517
+ if (G.chosenResource) {
518
+ G.chosenResource = undefined;
519
+ }
520
+ else {
521
+ player.passed = true;
522
+ }
523
+ }
524
+ else {
525
+ player.passed = true;
526
+ }
527
+ if (G.players.filter((p) => !p.passed && !p.isDropped).length == 0) {
528
+ G.players.forEach((p) => {
529
+ p.passed = p.isDropped;
530
+ });
531
+ G.phase = gamestate_1.Phase.Building;
532
+ if (G.map.name == 'India') {
533
+ G.citiesBuiltInCurrentRound = 0;
534
+ }
535
+ setCurrentPlayer(G, G.playerOrder[G.players.length - 1]);
536
+ if (G.players[G.currentPlayers[0]].isDropped && G.players.some((p) => !p.isDropped)) {
537
+ G.players[G.currentPlayers[0]].passed = true;
538
+ nextPlayerReverse(G);
539
+ }
540
+ }
541
+ else {
542
+ nextPlayerReverse(G);
543
+ }
544
+ if (G.map.name == 'India') {
545
+ while (G.players[G.currentPlayers[0]].passed) {
546
+ nextPlayerReverse(G);
547
+ }
548
+ }
549
+ break;
550
+ }
551
+ case gamestate_1.Phase.Building: {
552
+ player.passed = true;
553
+ if (G.players.filter((p) => !p.passed && !p.isDropped).length == 0) {
554
+ const maxCities = Math.max(...G.players.map((p) => p.cities.length));
555
+ if (G.step == 1) {
556
+ if (maxCities >= G.citiesToStep2 && G.map.name != 'Middle East') {
557
+ const powerPlant = G.actualMarket.shift();
558
+ G.log.push({
559
+ type: 'event',
560
+ event: `Starting Step 2, Power Plant ${powerPlant === null || powerPlant === void 0 ? void 0 : powerPlant.number} discarded.`,
561
+ });
562
+ G.step = 2;
563
+ // Spain & Portugal: put plants 18, 22 and 27 on top
564
+ if (G.map.name == 'Spain & Portugal') {
565
+ if (!G.powerPlantsDeck.find((p) => p.number == 18)) {
566
+ G.powerPlantsDeck.unshift(getPowerPlant(27));
567
+ G.powerPlantsDeck.unshift(getPowerPlant(22));
568
+ G.powerPlantsDeck.unshift(getPowerPlant(18));
569
+ }
570
+ }
571
+ addPowerPlant(G);
572
+ }
573
+ }
574
+ if (maxCities >= G.citiesToEndGame) {
575
+ G.phase = gamestate_1.Phase.GameEnd;
576
+ G.currentPlayers = [];
577
+ calculateCitiesPowered(G);
578
+ // Include payouts in phase 5 if there is a power outage in India.
579
+ if (G.map.name == 'India' && G.citiesBuiltInCurrentRound > G.players.length * 2) {
580
+ G.players.forEach((player) => {
581
+ const payment = G.paymentTable[player.citiesPowered] - 3 * player.cities.length;
582
+ player.money += Math.max(payment, 0);
583
+ player.totalIncome += Math.max(payment, 0);
584
+ });
585
+ }
586
+ G.log.push({ type: 'event', event: 'Game Ended!' });
587
+ }
588
+ else {
589
+ G.players.forEach((p) => {
590
+ p.passed = p.isDropped;
591
+ p.powerPlantsNotUsed = p.powerPlants.map((pp) => pp.number);
592
+ });
593
+ G.phase = gamestate_1.Phase.Bureaucracy;
594
+ G.currentPlayers = G.playerOrder.filter((p) => !G.players[p].passed && !G.players[p].isDropped);
595
+ if (G.map.name == 'India') {
596
+ // Compute the maximum number of cities each player can power.
597
+ G.players.forEach((player) => (player.targetCitiesPowered = calculateMaxCitiesPowered(player)));
598
+ // Output log for power outage.
599
+ if (G.citiesBuiltInCurrentRound > G.players.length * 2) {
600
+ G.log.push({
601
+ type: 'event',
602
+ event: `Power outage! ${G.citiesBuiltInCurrentRound} built this round, which is more than twice the number of players.`,
603
+ });
604
+ }
605
+ }
606
+ if (G.map.name == 'China' && G.step <= 2) {
607
+ rebuildPlantMarketForChina(G);
608
+ }
609
+ else if (G.futureMarket.length == 0) {
610
+ G.step = 3;
611
+ }
612
+ }
613
+ }
614
+ else {
615
+ nextPlayerReverse(G);
616
+ }
617
+ break;
618
+ }
619
+ case gamestate_1.Phase.Bureaucracy: {
620
+ player.passed = true;
621
+ const citiesPowered = Math.min(player.cities.length, player.citiesPowered);
622
+ let payment = G.paymentTable[citiesPowered];
623
+ // For the India map, if the number of cities built in the current round is more than twice
624
+ // the number of players, each player is penalized three Elektro per city (power outage).
625
+ if (G.map.name == 'India' && G.citiesBuiltInCurrentRound > G.players.length * 2) {
626
+ payment -= 3 * player.cities.length;
627
+ payment = Math.max(payment, 0); // No negative income
628
+ }
629
+ player.money += payment;
630
+ if (G.options.trackTotalSpent) {
631
+ player.totalIncome += payment;
632
+ }
633
+ player.citiesPowered = 0;
634
+ if (G.map.name == 'India') {
635
+ player.targetCitiesPowered = 0;
636
+ }
637
+ if (G.players.filter((p) => !p.passed && !p.isDropped).length == 0) {
638
+ const coalResupplyValue = Math.min(G.coalSupply, G.coalResupply[G.players.length - 2][G.step - 1]);
639
+ G.coalMarket += coalResupplyValue;
640
+ G.coalSupply -= coalResupplyValue;
641
+ let oilResupplyValue;
642
+ if (G.map.name == 'Middle East') {
643
+ if (G.oilMarket == 0) {
644
+ G.oilPrices = prices_1.default[gamestate_1.ResourceType.Oil];
645
+ }
646
+ oilResupplyValue = G.oilResupply[G.players.length - 2][G.step - 1];
647
+ for (let i = 0; i < oilResupplyValue; i++) {
648
+ if (G.oilSupply > 0) {
649
+ G.oilMarket++;
650
+ G.oilSupply--;
651
+ }
652
+ else {
653
+ // If we have more oil to stock than is in the supply, we shift the prices downward.
654
+ G.oilPrices.pop();
655
+ G.oilPrices.unshift(1);
656
+ }
657
+ }
658
+ }
659
+ else {
660
+ oilResupplyValue = Math.min(G.oilSupply, G.oilResupply[G.players.length - 2][G.step - 1]);
661
+ G.oilMarket += oilResupplyValue;
662
+ G.oilSupply -= oilResupplyValue;
663
+ }
664
+ const garbageResupplyValue = Math.min(G.garbageSupply, G.garbageResupply[G.players.length - 2][G.step - 1]);
665
+ G.garbageMarket += garbageResupplyValue;
666
+ G.garbageSupply -= garbageResupplyValue;
667
+ let uraniumResupplyValue = 0;
668
+ if (G.options.variant != 'recharged' ||
669
+ (G.options.map != 'Germany' && G.options.map != 'Italy') ||
670
+ !G.card39Bought) {
671
+ uraniumResupplyValue = Math.min(G.uraniumSupply, G.uraniumResupply[G.players.length - 2][G.step - 1]);
672
+ G.uraniumMarket += uraniumResupplyValue;
673
+ G.uraniumSupply -= uraniumResupplyValue;
674
+ }
675
+ G.log.push({
676
+ type: 'event',
677
+ event: `Resupplying resources: [${coalResupplyValue}, ${oilResupplyValue}, ${garbageResupplyValue}, ${uraniumResupplyValue}].`,
678
+ });
679
+ if (G.map.name == 'Middle East' && G.step == 2 && G.futureMarket.length > 0) {
680
+ // If we aren't about to enter step 3, discard top two plants instead of one.
681
+ let powerPlantToPush = G.futureMarket.pop();
682
+ G.log.push({
683
+ type: 'event',
684
+ event: `Putting Power Plant ${powerPlantToPush.number} on the bottom of the deck.`,
685
+ });
686
+ G.powerPlantsDeck.push(powerPlantToPush);
687
+ addPowerPlant(G);
688
+ // If Step 3 was drawn above, futureMarket will be empty so use actualMarket instead
689
+ powerPlantToPush = G.futureMarket.length ? G.futureMarket.pop() : G.actualMarket.pop();
690
+ G.log.push({
691
+ type: 'event',
692
+ event: `Putting Power Plant ${powerPlantToPush.number} on the bottom of the deck.`,
693
+ });
694
+ G.powerPlantsDeck.push(powerPlantToPush);
695
+ addPowerPlant(G);
696
+ }
697
+ else if (G.futureMarket.length > 0) {
698
+ if (G.map.name == 'Benelux' &&
699
+ (G.options.variant == 'original' || G.discardSmallestPlant)) {
700
+ const removedPlant = G.actualMarket[0];
701
+ G.log.push({
702
+ type: 'event',
703
+ event: `Removing smallest plant, Power Plant ${removedPlant.number}.`,
704
+ });
705
+ G.actualMarket.shift();
706
+ addPowerPlant(G);
707
+ G.discardSmallestPlant = false;
708
+ }
709
+ let powerPlantToPush;
710
+ if (G.map.name == 'Quebec') {
711
+ // For the Quebec map, ecological plants will never be put on the bottom of the deck.
712
+ const nonEcoPlants = G.futureMarket.filter((pp) => pp.type != gamestate_1.PowerPlantType.Wind);
713
+ powerPlantToPush = nonEcoPlants.pop();
714
+ if (powerPlantToPush) {
715
+ G.futureMarket = G.futureMarket.filter((pp) => pp.number != (powerPlantToPush === null || powerPlantToPush === void 0 ? void 0 : powerPlantToPush.number));
716
+ }
717
+ else {
718
+ const nonEcoPlants = G.actualMarket.filter((pp) => pp.type != gamestate_1.PowerPlantType.Wind);
719
+ powerPlantToPush = nonEcoPlants.pop();
720
+ if (powerPlantToPush) {
721
+ G.actualMarket = G.actualMarket.filter((pp) => pp.number != (powerPlantToPush === null || powerPlantToPush === void 0 ? void 0 : powerPlantToPush.number));
722
+ }
723
+ }
724
+ }
725
+ else {
726
+ powerPlantToPush = G.futureMarket.pop();
727
+ }
728
+ // This check covers the rare case in which a Quebec game might have a futures market consisting of
729
+ // all ecological plants. In that case, we do not draw a new plant.
730
+ if (powerPlantToPush) {
731
+ G.log.push({
732
+ type: 'event',
733
+ event: `Putting Power Plant ${powerPlantToPush.number} on the bottom of the deck.`,
734
+ });
735
+ G.powerPlantsDeck.push(powerPlantToPush);
736
+ addPowerPlant(G);
737
+ }
738
+ }
739
+ else if (G.actualMarket.length > 0 && (G.map.name != 'China' || G.step == 3)) {
740
+ G.log.push({ type: 'event', event: `Discarding Power Plant ${G.actualMarket[0].number}.` });
741
+ G.actualMarket.shift();
742
+ addPowerPlant(G);
743
+ }
744
+ G.round++;
745
+ setPlayerOrder(G);
746
+ G.players.forEach((p) => {
747
+ p.passed = p.isDropped;
748
+ });
749
+ G.auctionSkips = 0;
750
+ if (G.actualMarket.length > 0) {
751
+ G.phase = gamestate_1.Phase.Auction;
752
+ if (G.futureMarket.length == 0 && G.map.name != 'China') {
753
+ G.step = 3;
754
+ }
755
+ G.plantDiscountActive =
756
+ G.options.variant == 'recharged' && G.map.name != 'China' && G.map.name != 'Russia';
757
+ setCurrentPlayer(G, G.playerOrder[0]);
758
+ }
759
+ else {
760
+ toResourcesPhase(G);
761
+ }
762
+ }
763
+ else {
764
+ G.currentPlayers = G.playerOrder.filter((p) => !G.players[p].passed && !G.players[p].isDropped);
765
+ }
766
+ break;
767
+ }
768
+ }
769
+ break;
770
+ }
771
+ case move_1.MoveName.DiscardPowerPlant: {
772
+ utils_1.asserts(move);
773
+ if (!move.extra) {
774
+ G.log.push({
775
+ type: 'move',
776
+ player: playerNumber,
777
+ move,
778
+ simple: `${player.name} discards Power Plant ${move.data}.`,
779
+ pretty: `${playerNameHTML(player)} discards Power Plant <b>${move.data}</b>.`,
780
+ });
781
+ }
782
+ const powerPlant = player.powerPlants.find((p) => p.number == move.data);
783
+ player.powerPlants = player.powerPlants.filter((p) => p.number != move.data);
784
+ updatePlayerCapacity(player);
785
+ if (move.extra) {
786
+ const discarded = [];
787
+ switch (powerPlant.type) {
788
+ case gamestate_1.PowerPlantType.Coal:
789
+ G.coalSupply += move.extra[0];
790
+ player.coalLeft -= move.extra[0];
791
+ discarded.push(move.extra[0] + ' Coal');
792
+ break;
793
+ case gamestate_1.PowerPlantType.Oil:
794
+ G.oilSupply += move.extra[0];
795
+ player.oilLeft -= move.extra[0];
796
+ discarded.push(move.extra[0] + ' Oil');
797
+ break;
798
+ case gamestate_1.PowerPlantType.Garbage:
799
+ G.garbageSupply += move.extra[0];
800
+ player.garbageLeft -= move.extra[0];
801
+ discarded.push(move.extra[0] + ' Garbage');
802
+ break;
803
+ case gamestate_1.PowerPlantType.Uranium:
804
+ G.uraniumSupply += move.extra[0];
805
+ player.uraniumLeft -= move.extra[0];
806
+ discarded.push(move.extra[0] + ' Uranium');
807
+ break;
808
+ case gamestate_1.PowerPlantType.Hybrid:
809
+ if (move.extra[0] > 0) {
810
+ G.coalSupply += move.extra[0];
811
+ player.coalLeft -= move.extra[0];
812
+ discarded.push(move.extra[0] + ' Coal');
813
+ }
814
+ if (move.extra[1] > 0) {
815
+ G.oilSupply += move.extra[1];
816
+ player.oilLeft -= move.extra[1];
817
+ discarded.push(move.extra[1] + ' Oil');
818
+ }
819
+ break;
820
+ }
821
+ G.log.push({
822
+ type: 'move',
823
+ player: playerNumber,
824
+ move,
825
+ simple: `${player.name} discards Power Plant ${move.data} and ${discarded.join(', ')}.`,
826
+ pretty: `${playerNameHTML(player)} discards Power Plant <b>${move.data}</b> and ${discarded.join(', ')}.`,
827
+ });
828
+ if (G.map.name != 'China' || G.step == 3) {
829
+ addPowerPlant(G);
830
+ }
831
+ G.players.forEach((p) => {
832
+ p.bid = 0;
833
+ p.passed = p.isDropped;
834
+ });
835
+ if (G.players.some((p) => !p.skipAuction && !p.isDropped)) {
836
+ nextPlayerAuction(G, true);
837
+ }
838
+ else {
839
+ toResourcesPhase(G);
840
+ }
841
+ }
842
+ else {
843
+ const toDiscard = [];
844
+ let hybridCapacityUsed = player.hybridCapacity > 0 ? Math.max(0, player.oilLeft - player.oilCapacity) : 0;
845
+ if (player.coalCapacity + player.hybridCapacity < player.coalLeft + hybridCapacityUsed) {
846
+ toDiscard.push(gamestate_1.ResourceType.Coal);
847
+ }
848
+ hybridCapacityUsed = player.hybridCapacity > 0 ? Math.max(0, player.coalLeft - player.coalCapacity) : 0;
849
+ if (player.oilCapacity + player.hybridCapacity < player.oilLeft + hybridCapacityUsed) {
850
+ toDiscard.push(gamestate_1.ResourceType.Oil);
851
+ }
852
+ if (player.garbageLeft > player.garbageCapacity) {
853
+ G.garbageSupply += player.garbageLeft - player.garbageCapacity;
854
+ player.garbageLeft = player.garbageCapacity;
855
+ }
856
+ if (player.uraniumLeft > player.uraniumCapacity) {
857
+ G.uraniumSupply += player.uraniumLeft - player.uraniumCapacity;
858
+ player.uraniumLeft = player.uraniumCapacity;
859
+ }
860
+ if (toDiscard.length == 1) {
861
+ if (toDiscard[0] == gamestate_1.ResourceType.Coal) {
862
+ G.coalSupply += player.coalLeft - player.coalCapacity;
863
+ player.coalLeft = player.coalCapacity;
864
+ }
865
+ else if (toDiscard[0] == gamestate_1.ResourceType.Oil) {
866
+ G.oilSupply += player.oilLeft - player.oilCapacity;
867
+ player.oilLeft = player.oilCapacity;
868
+ }
869
+ toDiscard.pop();
870
+ }
871
+ if (toDiscard.length == 0) {
872
+ if (G.map.name != 'China' || G.step == 3) {
873
+ addPowerPlant(G);
874
+ }
875
+ G.players.forEach((p) => {
876
+ p.bid = 0;
877
+ p.passed = p.isDropped;
878
+ });
879
+ if (G.players.some((p) => !p.skipAuction && !p.isDropped)) {
880
+ nextPlayerAuction(G, true);
881
+ }
882
+ else {
883
+ toResourcesPhase(G);
884
+ }
885
+ }
886
+ }
887
+ break;
888
+ }
889
+ case move_1.MoveName.DiscardResources: {
890
+ utils_1.asserts(move);
891
+ G.log.push({
892
+ type: 'move',
893
+ player: playerNumber,
894
+ move,
895
+ simple: `${player.name} discarded a ${move.data}.`,
896
+ pretty: `${playerNameHTML(player)} discarded a <b>${move.data}</b>.`,
897
+ });
898
+ if (move.data == gamestate_1.ResourceType.Coal) {
899
+ player.coalLeft--;
900
+ G.coalSupply++;
901
+ }
902
+ else if (move.data == gamestate_1.ResourceType.Oil) {
903
+ player.oilLeft--;
904
+ G.oilSupply++;
905
+ }
906
+ const toDiscard = [];
907
+ let hybridCapacityUsed = player.hybridCapacity > 0 ? Math.max(0, player.oilLeft - player.oilCapacity) : 0;
908
+ if (player.coalCapacity + player.hybridCapacity < player.coalLeft + hybridCapacityUsed) {
909
+ toDiscard.push(gamestate_1.ResourceType.Coal);
910
+ }
911
+ hybridCapacityUsed = player.hybridCapacity > 0 ? Math.max(0, player.coalLeft - player.coalCapacity) : 0;
912
+ if (player.oilCapacity + player.hybridCapacity < player.oilLeft + hybridCapacityUsed) {
913
+ toDiscard.push(gamestate_1.ResourceType.Oil);
914
+ }
915
+ if (toDiscard.length == 1) {
916
+ if (toDiscard[0] == gamestate_1.ResourceType.Coal) {
917
+ player.coalLeft--;
918
+ }
919
+ else if (toDiscard[0] == gamestate_1.ResourceType.Oil) {
920
+ player.oilLeft--;
921
+ }
922
+ toDiscard.pop();
923
+ }
924
+ if (toDiscard.length == 0) {
925
+ if (G.map.name != 'China' || G.step == 3) {
926
+ addPowerPlant(G);
927
+ }
928
+ G.players.forEach((p) => {
929
+ p.bid = 0;
930
+ p.passed = p.isDropped;
931
+ });
932
+ if (G.players.some((p) => !p.skipAuction && !p.isDropped)) {
933
+ nextPlayerAuction(G, true);
934
+ }
935
+ else {
936
+ toResourcesPhase(G);
937
+ }
938
+ }
939
+ break;
940
+ }
941
+ case move_1.MoveName.BuyResource: {
942
+ utils_1.asserts(move);
943
+ G.chosenResource = move.data.resource;
944
+ let price;
945
+ switch (move.data.resource) {
946
+ case gamestate_1.ResourceType.Coal: {
947
+ if (G.coalMarket == 0) {
948
+ price = 8;
949
+ player.coalLeft++;
950
+ G.coalSupply--;
951
+ move.fromSupply = true;
952
+ }
953
+ else {
954
+ const coalPrices = (_c = G.coalPrices) !== null && _c !== void 0 ? _c : prices_1.default[gamestate_1.ResourceType.Coal];
955
+ price = coalPrices[coalPrices.length - G.coalMarket];
956
+ player.coalLeft++;
957
+ G.coalMarket--;
958
+ }
959
+ break;
960
+ }
961
+ case gamestate_1.ResourceType.Oil: {
962
+ const oilPrices = (_d = G.oilPrices) !== null && _d !== void 0 ? _d : prices_1.default[gamestate_1.ResourceType.Oil];
963
+ price = oilPrices[oilPrices.length - G.oilMarket];
964
+ player.oilLeft++;
965
+ G.oilMarket--;
966
+ break;
967
+ }
968
+ case gamestate_1.ResourceType.Garbage: {
969
+ const garbagePrices = (_e = G.garbagePrices) !== null && _e !== void 0 ? _e : prices_1.default[gamestate_1.ResourceType.Garbage];
970
+ price = garbagePrices[garbagePrices.length - G.garbageMarket];
971
+ // $1 cheaper for players in Wien in Central Europe
972
+ if (G.map.name == 'Central Europe') {
973
+ const wienCity = player.cities.filter((c) => c.name == 'Wien');
974
+ if ((wienCity === null || wienCity === void 0 ? void 0 : wienCity.length) > 0) {
975
+ price--;
976
+ }
977
+ }
978
+ player.garbageLeft++;
979
+ G.garbageMarket--;
980
+ break;
981
+ }
982
+ case gamestate_1.ResourceType.Uranium: {
983
+ const uraniumPrices = (_f = G.uraniumPrices) !== null && _f !== void 0 ? _f : prices_1.default[gamestate_1.ResourceType.Uranium];
984
+ price = uraniumPrices[uraniumPrices.length - G.uraniumMarket];
985
+ player.uraniumLeft++;
986
+ G.uraniumMarket--;
987
+ break;
988
+ }
989
+ }
990
+ player.money -= price;
991
+ if (G.options.trackTotalSpent) {
992
+ player.totalSpentResources += price;
993
+ }
994
+ G.log.push({
995
+ type: 'move',
996
+ player: playerNumber,
997
+ move,
998
+ simple: `${player.name} buys ${move.data.resource} for $${price}.`,
999
+ pretty: `${playerNameHTML(player)} buys <b>${move.data.resource}</b> for <span style="color: green">$${price}</span>.`,
1000
+ });
1001
+ break;
1002
+ }
1003
+ case move_1.MoveName.Build: {
1004
+ utils_1.asserts(move);
1005
+ const position = G.players.filter((p) => p.cities.find((c) => c.name == move.data.name)).length;
1006
+ player.cities.push({ name: move.data.name, position });
1007
+ player.money -= move.data.price;
1008
+ if (G.options.trackTotalSpent) {
1009
+ player.totalSpentCities += 10 + position * 5;
1010
+ player.totalSpentConnections += move.data.price - (10 + position * 5);
1011
+ }
1012
+ G.log.push({
1013
+ type: 'move',
1014
+ player: playerNumber,
1015
+ move,
1016
+ simple: `${player.name} builds on ${move.data.name} for $${move.data.price}.`,
1017
+ pretty: `${playerNameHTML(player)} builds on <b>${move.data.name}</b> for <span style="color: green">$${move.data.price}</span>.`,
1018
+ });
1019
+ if (G.map.name == 'India') {
1020
+ G.citiesBuiltInCurrentRound++;
1021
+ }
1022
+ if (G.options.variant == 'original') {
1023
+ if (G.actualMarket.length > 0 &&
1024
+ player.cities.length >= G.actualMarket[0].number &&
1025
+ G.map.name != 'China' &&
1026
+ G.map.name != 'Russia') {
1027
+ G.actualMarket.shift();
1028
+ addPowerPlant(G);
1029
+ }
1030
+ }
1031
+ break;
1032
+ }
1033
+ case move_1.MoveName.UsePowerPlant: {
1034
+ utils_1.asserts(move);
1035
+ player.powerPlantsNotUsed = player.powerPlantsNotUsed.filter((x) => x != move.data.powerPlant);
1036
+ move.data.resourcesSpent.forEach((resourceType) => {
1037
+ switch (resourceType) {
1038
+ case gamestate_1.ResourceType.Coal:
1039
+ player.coalLeft--;
1040
+ G.coalSupply++;
1041
+ break;
1042
+ case gamestate_1.ResourceType.Oil:
1043
+ player.oilLeft--;
1044
+ G.oilSupply++;
1045
+ break;
1046
+ case gamestate_1.ResourceType.Garbage:
1047
+ player.garbageLeft--;
1048
+ G.garbageSupply++;
1049
+ break;
1050
+ case gamestate_1.ResourceType.Uranium:
1051
+ player.uraniumLeft--;
1052
+ G.uraniumSupply++;
1053
+ break;
1054
+ }
1055
+ });
1056
+ player.citiesPowered += move.data.citiesPowered;
1057
+ G.log.push({
1058
+ type: 'move',
1059
+ player: playerNumber,
1060
+ move,
1061
+ simple: `${player.name} uses Power Plant ${move.data.powerPlant}.`,
1062
+ pretty: `${playerNameHTML(player)} uses Power Plant <b>${move.data.powerPlant}</b>.`,
1063
+ });
1064
+ break;
1065
+ }
1066
+ case move_1.MoveName.Undo: {
1067
+ utils_1.asserts(move);
1068
+ const lastMove = player.lastMove;
1069
+ switch (lastMove === null || lastMove === void 0 ? void 0 : lastMove.name) {
1070
+ case move_1.MoveName.ChoosePowerPlant: {
1071
+ if (lastMove.data == 39) {
1072
+ G.card39Bought = false;
1073
+ }
1074
+ if (lastMove.usedPlantDiscount) {
1075
+ G.plantDiscountActive = true;
1076
+ }
1077
+ G.chosenPowerPlant = undefined;
1078
+ G.auctioningPlayer = undefined;
1079
+ G.currentPlayers = [player.id];
1080
+ G.log.pop();
1081
+ break;
1082
+ }
1083
+ case move_1.MoveName.BuyResource: {
1084
+ let price;
1085
+ switch (lastMove.data.resource) {
1086
+ case gamestate_1.ResourceType.Coal:
1087
+ if (lastMove.fromSupply) {
1088
+ price = 8;
1089
+ player.coalLeft--;
1090
+ G.coalSupply++;
1091
+ }
1092
+ else {
1093
+ player.coalLeft--;
1094
+ G.coalMarket++;
1095
+ const coalPrices = (_g = G.coalPrices) !== null && _g !== void 0 ? _g : prices_1.default[gamestate_1.ResourceType.Coal];
1096
+ price = coalPrices[coalPrices.length - G.coalMarket];
1097
+ }
1098
+ break;
1099
+ case gamestate_1.ResourceType.Oil: {
1100
+ player.oilLeft--;
1101
+ G.oilMarket++;
1102
+ const oilPrices = (_h = G.oilPrices) !== null && _h !== void 0 ? _h : prices_1.default[gamestate_1.ResourceType.Oil];
1103
+ price = oilPrices[oilPrices.length - G.oilMarket];
1104
+ break;
1105
+ }
1106
+ case gamestate_1.ResourceType.Garbage: {
1107
+ player.garbageLeft--;
1108
+ G.garbageMarket++;
1109
+ const garbagePrices = (_j = G.garbagePrices) !== null && _j !== void 0 ? _j : prices_1.default[gamestate_1.ResourceType.Garbage];
1110
+ price = garbagePrices[garbagePrices.length - G.garbageMarket];
1111
+ // $1 cheaper for players in Wien in Central Europe
1112
+ if (G.map.name == 'Central Europe') {
1113
+ const wienCity = player.cities.filter((c) => c.name == 'Wien');
1114
+ if ((wienCity === null || wienCity === void 0 ? void 0 : wienCity.length) > 0) {
1115
+ price--;
1116
+ }
1117
+ }
1118
+ break;
1119
+ }
1120
+ case gamestate_1.ResourceType.Uranium: {
1121
+ player.uraniumLeft--;
1122
+ G.uraniumMarket++;
1123
+ const uraniumPrices = (_k = G.uraniumPrices) !== null && _k !== void 0 ? _k : prices_1.default[gamestate_1.ResourceType.Uranium];
1124
+ price = uraniumPrices[uraniumPrices.length - G.uraniumMarket];
1125
+ break;
1126
+ }
1127
+ }
1128
+ player.money += price;
1129
+ if (G.options.trackTotalSpent) {
1130
+ player.totalSpentResources -= price;
1131
+ }
1132
+ if (G.map.name == 'India') {
1133
+ G.chosenResource = undefined;
1134
+ }
1135
+ G.log.pop();
1136
+ break;
1137
+ }
1138
+ case move_1.MoveName.Build: {
1139
+ player.cities.pop();
1140
+ player.money += lastMove.data.price;
1141
+ const position = G.players.filter((p) => p.cities.find((c) => c.name == lastMove.data.name)).length;
1142
+ if (G.options.trackTotalSpent) {
1143
+ player.totalSpentCities -= 10 + position * 5;
1144
+ player.totalSpentConnections -= lastMove.data.price - (10 + position * 5);
1145
+ }
1146
+ G.log.pop();
1147
+ if (G.map.name == 'India') {
1148
+ G.citiesBuiltInCurrentRound--;
1149
+ }
1150
+ break;
1151
+ }
1152
+ case move_1.MoveName.UsePowerPlant: {
1153
+ player.powerPlantsNotUsed.push(lastMove.data.powerPlant);
1154
+ lastMove.data.resourcesSpent.forEach((resourceType) => {
1155
+ switch (resourceType) {
1156
+ case gamestate_1.ResourceType.Coal:
1157
+ player.coalLeft++;
1158
+ G.coalSupply--;
1159
+ break;
1160
+ case gamestate_1.ResourceType.Oil:
1161
+ player.oilLeft++;
1162
+ G.oilSupply--;
1163
+ break;
1164
+ case gamestate_1.ResourceType.Garbage:
1165
+ player.garbageLeft++;
1166
+ G.garbageSupply--;
1167
+ break;
1168
+ case gamestate_1.ResourceType.Uranium:
1169
+ player.uraniumLeft++;
1170
+ G.uraniumSupply--;
1171
+ break;
1172
+ }
1173
+ });
1174
+ player.citiesPowered -= lastMove.data.citiesPowered;
1175
+ const reverseLog = G.log.slice().reverse();
1176
+ const index = G.log.length - reverseLog.findIndex((l) => l.type == 'move' && l.player == player.id) - 1;
1177
+ G.log.splice(index, 1);
1178
+ break;
1179
+ }
1180
+ }
1181
+ }
1182
+ }
1183
+ player.availableMoves = null;
1184
+ if (move.name == move_1.MoveName.Undo) {
1185
+ const reverseLog = G.log.slice().reverse();
1186
+ const logMove = reverseLog.find((m) => m.type == 'move' && m.player == player.id);
1187
+ player.lastMove = logMove === null || logMove === void 0 ? void 0 : logMove.move;
1188
+ }
1189
+ else {
1190
+ player.lastMove = move;
1191
+ }
1192
+ G.cardsLeft = G.powerPlantsDeck.length;
1193
+ G.nextCardWeak = G.options.variant == 'recharged' && G.cardsLeft > 0 && G.powerPlantsDeck[0].number <= 15;
1194
+ G.currentPlayers.forEach((p) => (G.players[p].availableMoves = available_moves_1.availableMoves(G, G.players[p])));
1195
+ return G;
1196
+ }
1197
+ exports.move = move;
1198
+ function moveAI(G, playerNumber) {
1199
+ const player = G.players[playerNumber];
1200
+ const availableMoves = player.availableMoves;
1201
+ let chosenMove = { name: move_1.MoveName.Pass, data: true };
1202
+ switch (G.phase) {
1203
+ case gamestate_1.Phase.Auction: {
1204
+ if (availableMoves === null || availableMoves === void 0 ? void 0 : availableMoves.ChoosePowerPlant) {
1205
+ if (!availableMoves.Pass ||
1206
+ (Math.random() > 0.5 && player.money - availableMoves.ChoosePowerPlant[0] >= 20)) {
1207
+ chosenMove = {
1208
+ name: move_1.MoveName.ChoosePowerPlant,
1209
+ data: chooseRandom(availableMoves.ChoosePowerPlant),
1210
+ };
1211
+ }
1212
+ else {
1213
+ chosenMove = { name: move_1.MoveName.Pass, data: true };
1214
+ }
1215
+ }
1216
+ else if (availableMoves === null || availableMoves === void 0 ? void 0 : availableMoves.Bid) {
1217
+ if (!availableMoves.Pass ||
1218
+ (availableMoves.Bid.length > 0 &&
1219
+ Math.random() > 0.5 &&
1220
+ player.money - (availableMoves === null || availableMoves === void 0 ? void 0 : availableMoves.Bid[0]) >= 15)) {
1221
+ if (G.options.fastBid) {
1222
+ const bid = Math.floor((Math.random() * availableMoves.Bid.length) / 2);
1223
+ chosenMove = { name: move_1.MoveName.Bid, data: availableMoves === null || availableMoves === void 0 ? void 0 : availableMoves.Bid[bid] };
1224
+ }
1225
+ else {
1226
+ chosenMove = { name: move_1.MoveName.Bid, data: availableMoves === null || availableMoves === void 0 ? void 0 : availableMoves.Bid[0] };
1227
+ }
1228
+ }
1229
+ else {
1230
+ chosenMove = { name: move_1.MoveName.Pass, data: true };
1231
+ }
1232
+ }
1233
+ else if (availableMoves === null || availableMoves === void 0 ? void 0 : availableMoves.DiscardPowerPlant) {
1234
+ chosenMove = { name: move_1.MoveName.DiscardPowerPlant, data: player.powerPlants[0].number };
1235
+ }
1236
+ else if (availableMoves === null || availableMoves === void 0 ? void 0 : availableMoves.DiscardResources) {
1237
+ chosenMove = { name: move_1.MoveName.DiscardResources, data: chooseRandom(availableMoves.DiscardResources) };
1238
+ }
1239
+ break;
1240
+ }
1241
+ case gamestate_1.Phase.Resources: {
1242
+ if ((availableMoves === null || availableMoves === void 0 ? void 0 : availableMoves.BuyResource) && player.money > 20) {
1243
+ const buyCoal = availableMoves.BuyResource.find((r) => r.resource == gamestate_1.ResourceType.Coal);
1244
+ const buyOil = availableMoves.BuyResource.find((r) => r.resource == gamestate_1.ResourceType.Oil);
1245
+ const buyGarbage = availableMoves.BuyResource.find((r) => r.resource == gamestate_1.ResourceType.Garbage);
1246
+ const buyUranium = availableMoves.BuyResource.find((r) => r.resource == gamestate_1.ResourceType.Uranium);
1247
+ if (buyCoal && player.coalLeft < (player.coalCapacity + player.hybridCapacity) / 2) {
1248
+ chosenMove = { name: move_1.MoveName.BuyResource, data: buyCoal };
1249
+ }
1250
+ else if (buyOil && player.oilLeft < (player.oilCapacity + player.hybridCapacity) / 2) {
1251
+ chosenMove = { name: move_1.MoveName.BuyResource, data: buyOil };
1252
+ }
1253
+ else if (buyGarbage && player.garbageLeft < player.garbageCapacity / 2) {
1254
+ chosenMove = { name: move_1.MoveName.BuyResource, data: buyGarbage };
1255
+ }
1256
+ else if (buyUranium && player.uraniumLeft < player.uraniumCapacity / 2) {
1257
+ chosenMove = { name: move_1.MoveName.BuyResource, data: buyUranium };
1258
+ }
1259
+ }
1260
+ break;
1261
+ }
1262
+ case gamestate_1.Phase.Building: {
1263
+ const capacity = player.powerPlants.map((pp) => pp.citiesPowered).reduce((a, b) => a + b);
1264
+ if ((availableMoves === null || availableMoves === void 0 ? void 0 : availableMoves.Build) && (player.money >= 30 || capacity > player.cities.length)) {
1265
+ const minPrice = availableMoves.Build.sort((a, b) => a.price - b.price)[0].price;
1266
+ const cheapestCities = availableMoves.Build.filter((x) => x.price == minPrice);
1267
+ chosenMove = { name: move_1.MoveName.Build, data: chooseRandom(cheapestCities) };
1268
+ }
1269
+ break;
1270
+ }
1271
+ case gamestate_1.Phase.Bureaucracy: {
1272
+ if ((availableMoves === null || availableMoves === void 0 ? void 0 : availableMoves.UsePowerPlant) && player.cities.length > player.citiesPowered) {
1273
+ chosenMove = {
1274
+ name: move_1.MoveName.UsePowerPlant,
1275
+ data: availableMoves.UsePowerPlant.sort((a, b) => a.citiesPowered - b.citiesPowered)[0],
1276
+ };
1277
+ }
1278
+ break;
1279
+ }
1280
+ }
1281
+ console.log('ai move', chosenMove);
1282
+ return move(G, chosenMove, playerNumber);
1283
+ }
1284
+ exports.moveAI = moveAI;
1285
+ function chooseRandom(moves) {
1286
+ return moves[Math.floor(Math.random() * moves.length)];
1287
+ }
1288
+ function ended(G) {
1289
+ return G.phase == gamestate_1.Phase.GameEnd;
1290
+ }
1291
+ exports.ended = ended;
1292
+ function scores(G) {
1293
+ return ended(G) ? G.players.map((p) => p.citiesPowered) : G.players.map((_) => 0);
1294
+ }
1295
+ exports.scores = scores;
1296
+ function reconstructState(gameState, to) {
1297
+ const initialState = getBaseState(gameState);
1298
+ const G = lodash_1.cloneDeep(initialState);
1299
+ if (to != undefined && gameState.seed == 'secret') {
1300
+ if (gameState.knownPowerPlantDeck) {
1301
+ G.map = gameState.map;
1302
+ G.powerPlantsDeck = lodash_1.cloneDeep(gameState.knownPowerPlantDeck);
1303
+ G.actualMarket = G.powerPlantsDeck.splice(0, 4);
1304
+ G.futureMarket = G.powerPlantsDeck.splice(0, 4);
1305
+ G.players[G.currentPlayers[0]].availableMoves = available_moves_1.availableMoves(G, G.players[G.currentPlayers[0]]);
1306
+ G.powerPlantDeckAfterStep3 = gameState.knownPowerPlantDeckStep3;
1307
+ G.knownPowerPlantDeck = G.actualMarket.concat(G.futureMarket);
1308
+ }
1309
+ }
1310
+ const log = to != null ? gameState.log.slice(0, to) : gameState.log;
1311
+ for (const item of log) {
1312
+ switch (item.type) {
1313
+ case 'event': {
1314
+ if (item.event.endsWith('was dropped')) {
1315
+ const playerNum = +item.event.split(' ')[1];
1316
+ G.players[playerNum].isDropped = true;
1317
+ }
1318
+ break;
1319
+ }
1320
+ case 'move': {
1321
+ move(G, item.move, item.player, true);
1322
+ break;
1323
+ }
1324
+ }
1325
+ }
1326
+ return G;
1327
+ }
1328
+ exports.reconstructState = reconstructState;
1329
+ function updatePlayerCapacity(player) {
1330
+ player.coalCapacity =
1331
+ player.oilCapacity =
1332
+ player.garbageCapacity =
1333
+ player.uraniumCapacity =
1334
+ player.hybridCapacity =
1335
+ 0;
1336
+ player.powerPlants.forEach((powerPlant) => {
1337
+ switch (powerPlant.type) {
1338
+ case gamestate_1.PowerPlantType.Coal: {
1339
+ player.coalCapacity += powerPlant.cost * 2;
1340
+ break;
1341
+ }
1342
+ case gamestate_1.PowerPlantType.Oil: {
1343
+ player.oilCapacity += powerPlant.cost * 2;
1344
+ break;
1345
+ }
1346
+ case gamestate_1.PowerPlantType.Garbage: {
1347
+ if (powerPlant.storage) {
1348
+ // For the India map, garbage plants have cost one higher, but have no additional storage.
1349
+ player.garbageCapacity += powerPlant.storage;
1350
+ }
1351
+ else {
1352
+ player.garbageCapacity += powerPlant.cost * 2;
1353
+ }
1354
+ break;
1355
+ }
1356
+ case gamestate_1.PowerPlantType.Uranium: {
1357
+ player.uraniumCapacity += powerPlant.cost * 2;
1358
+ break;
1359
+ }
1360
+ case gamestate_1.PowerPlantType.Hybrid: {
1361
+ player.hybridCapacity += powerPlant.cost * 2;
1362
+ break;
1363
+ }
1364
+ }
1365
+ });
1366
+ }
1367
+ function addPowerPlant(G) {
1368
+ let powerPlant = G.powerPlantsDeck.shift();
1369
+ if (powerPlant) {
1370
+ if (G.step == 3) {
1371
+ if (G.knownPowerPlantDeckStep3) {
1372
+ G.knownPowerPlantDeckStep3.push(powerPlant);
1373
+ }
1374
+ }
1375
+ else {
1376
+ if (G.knownPowerPlantDeck) {
1377
+ G.knownPowerPlantDeck.push(powerPlant);
1378
+ }
1379
+ }
1380
+ if (G.options.variant == 'original' && G.map.name != 'China') {
1381
+ const maxCities = Math.max(...G.players.map((p) => p.cities.length));
1382
+ while (powerPlant.number <= maxCities) {
1383
+ G.log.push({
1384
+ type: 'event',
1385
+ event: `Power plant ${powerPlant === null || powerPlant === void 0 ? void 0 : powerPlant.number} discarded.`,
1386
+ });
1387
+ if (G.powerPlantsDeck.length > 0) {
1388
+ powerPlant = G.powerPlantsDeck.shift();
1389
+ if (G.step == 3) {
1390
+ if (G.knownPowerPlantDeckStep3) {
1391
+ G.knownPowerPlantDeckStep3.push(powerPlant);
1392
+ }
1393
+ }
1394
+ else {
1395
+ if (G.knownPowerPlantDeck) {
1396
+ G.knownPowerPlantDeck.push(powerPlant);
1397
+ }
1398
+ }
1399
+ }
1400
+ else {
1401
+ break;
1402
+ }
1403
+ }
1404
+ }
1405
+ let skipAdd = false;
1406
+ if (powerPlant.number == 99) {
1407
+ if (G.powerPlantDeckAfterStep3) {
1408
+ G.powerPlantsDeck = G.powerPlantDeckAfterStep3;
1409
+ }
1410
+ else if (G.map.name == 'China') {
1411
+ G.step = 3;
1412
+ }
1413
+ else {
1414
+ G.powerPlantsDeck = utils_1.shuffle(G.powerPlantsDeck, G.seed);
1415
+ }
1416
+ if (G.phase != gamestate_1.Phase.Auction) {
1417
+ if (G.map.name == 'Middle East' && G.step == 1) {
1418
+ // Add step 3 card to market, then trigger step 2 process.
1419
+ const market = [...G.actualMarket, ...G.futureMarket, powerPlant];
1420
+ market.sort((a, b) => a.number - b.number);
1421
+ G.actualMarket = market.slice(0, 4);
1422
+ G.futureMarket = market.slice(4);
1423
+ enterStepTwoMiddleEast(G);
1424
+ skipAdd = true;
1425
+ }
1426
+ else {
1427
+ const powerPlantDiscarded = G.actualMarket.shift();
1428
+ G.log.push({
1429
+ type: 'event',
1430
+ event: `Step 3 will begin next phase, Power Plant ${powerPlantDiscarded === null || powerPlantDiscarded === void 0 ? void 0 : powerPlantDiscarded.number} discarded.`,
1431
+ });
1432
+ const market = [...G.actualMarket, ...G.futureMarket];
1433
+ market.sort((a, b) => a.number - b.number);
1434
+ G.actualMarket = market;
1435
+ G.futureMarket = [];
1436
+ }
1437
+ }
1438
+ }
1439
+ else {
1440
+ if (G.plantDiscountActive && powerPlant.number < G.actualMarket[0].number) {
1441
+ G.log.push({
1442
+ type: 'event',
1443
+ event: `Power Plant ${powerPlant.number} drawn from the deck and discarded.`,
1444
+ });
1445
+ G.plantDiscountActive = false;
1446
+ addPowerPlant(G);
1447
+ return;
1448
+ }
1449
+ else {
1450
+ G.log.push({ type: 'event', event: `Power Plant ${powerPlant.number} drawn from the deck.` });
1451
+ }
1452
+ }
1453
+ if (G.map.name == 'China' && powerPlant.type != gamestate_1.PowerPlantType.Step3) {
1454
+ const market = [...G.actualMarket, powerPlant];
1455
+ market.sort((a, b) => a.number - b.number);
1456
+ G.actualMarket = market;
1457
+ }
1458
+ else {
1459
+ if (!skipAdd) {
1460
+ const market = [...G.actualMarket, ...G.futureMarket, powerPlant];
1461
+ market.sort((a, b) => a.number - b.number);
1462
+ if (G.futureMarket.length == 0) {
1463
+ if (G.map.name == 'Russia') {
1464
+ // Only four plants in market.
1465
+ G.actualMarket = market.slice(0, 4);
1466
+ G.futureMarket = [];
1467
+ }
1468
+ else {
1469
+ G.actualMarket = market.slice(0, 6);
1470
+ G.futureMarket = [];
1471
+ }
1472
+ }
1473
+ else {
1474
+ if (G.map.name == 'Russia') {
1475
+ // Only 3 plants in actual market and 3 in future market.
1476
+ G.actualMarket = market.slice(0, 3);
1477
+ G.futureMarket = market.slice(3);
1478
+ }
1479
+ else if (G.map.name == 'Benelux' && market[4].type == gamestate_1.PowerPlantType.Wind) {
1480
+ // Add extra ecological plant to actual market.
1481
+ G.actualMarket = market.slice(0, 5);
1482
+ G.futureMarket = market.slice(5);
1483
+ }
1484
+ else {
1485
+ G.actualMarket = market.slice(0, 4);
1486
+ G.futureMarket = market.slice(4);
1487
+ }
1488
+ }
1489
+ if (G.map.name == 'Middle East' && G.step == 1) {
1490
+ removePlantsForMiddleEastStep1(G);
1491
+ }
1492
+ }
1493
+ }
1494
+ }
1495
+ }
1496
+ // During step 1 for the Middle East map, we remove garbage and uranium plants from the actual market.
1497
+ // If the number is 6, 11, or 14, the plant is removed from the game. Otherwise, it's put under the deck.
1498
+ function removePlantsForMiddleEastStep1(G) {
1499
+ let plantToRemove = G.actualMarket.find((pp) => pp.type == gamestate_1.PowerPlantType.Garbage || pp.type == gamestate_1.PowerPlantType.Uranium);
1500
+ while (plantToRemove) {
1501
+ removePowerPlant(G, plantToRemove);
1502
+ if ([6, 11, 14].includes(plantToRemove.number)) {
1503
+ G.log.push({
1504
+ type: 'event',
1505
+ event: `Removing Power Plant ${plantToRemove.number} from game.`,
1506
+ });
1507
+ }
1508
+ else {
1509
+ G.powerPlantsDeck.push(plantToRemove);
1510
+ G.log.push({
1511
+ type: 'event',
1512
+ event: `Sending Power Plant ${plantToRemove.number} to the bottom of the deck.`,
1513
+ });
1514
+ }
1515
+ // Prevent infinite loop cycling through power plants.
1516
+ const availableFuturePlants = G.futureMarket.filter((pp) => pp.type != gamestate_1.PowerPlantType.Garbage && pp.type != gamestate_1.PowerPlantType.Uranium);
1517
+ const nextFuturePlantNumber = availableFuturePlants.length > 0 ? availableFuturePlants[0].number : 100;
1518
+ if (G.powerPlantsDeck.filter((pp) => (pp.type != gamestate_1.PowerPlantType.Garbage && pp.type != gamestate_1.PowerPlantType.Uranium) ||
1519
+ pp.number > nextFuturePlantNumber).length == 0) {
1520
+ G.log.push({
1521
+ type: 'event',
1522
+ event: 'No suitable power plants available to draw.',
1523
+ });
1524
+ break;
1525
+ }
1526
+ addPowerPlant(G);
1527
+ plantToRemove = G.actualMarket.find((pp) => pp.type == gamestate_1.PowerPlantType.Garbage || pp.type == gamestate_1.PowerPlantType.Uranium);
1528
+ }
1529
+ }
1530
+ function enterStepTwoMiddleEast(G) {
1531
+ // Shuffle deck of remaining power plants and put step 3 card back underneath.
1532
+ const step3 = G.futureMarket.pop();
1533
+ G.powerPlantsDeck = utils_1.shuffle(G.powerPlantsDeck, G.seed);
1534
+ G.powerPlantsDeck.push(step3);
1535
+ // Draw new plant to replace step 3 card.
1536
+ addPowerPlant(G);
1537
+ // Discard two lowest power plants from current market.
1538
+ G.log.push({
1539
+ type: 'event',
1540
+ event: 'Step 2 will begin next phase, discarding two power plants.',
1541
+ });
1542
+ G.step = 2;
1543
+ const powerPlantDiscarded1 = G.actualMarket.shift();
1544
+ if (powerPlantDiscarded1) {
1545
+ G.log.push({
1546
+ type: 'event',
1547
+ event: `Power Plant ${powerPlantDiscarded1.number} discarded to start step 2.`,
1548
+ });
1549
+ addPowerPlant(G);
1550
+ }
1551
+ const powerPlantDiscarded2 = G.actualMarket.shift();
1552
+ if (powerPlantDiscarded2) {
1553
+ G.log.push({
1554
+ type: 'event',
1555
+ event: `Power Plant ${powerPlantDiscarded2.number} discarded to start step 2.`,
1556
+ });
1557
+ addPowerPlant(G);
1558
+ }
1559
+ }
1560
+ function rebuildPlantMarketForChina(G) {
1561
+ /*At the beginning of phase 5, the players fill the power plant market with new power plants. Depending on the
1562
+ number of players, the players always add a minimum of 1, 2, or 3 power plants to the market from the supply:
1563
+ with 2 and 3 players, add at least 1 power plant.
1564
+ with 4 and 5 players, add at least 2 power plants.
1565
+ with 6 players, add at least 3 power plants.
1566
+ The players add more than the minimum if the number of plants in the market is still more than 1 less than the number of players.
1567
+ Exception: with 2 players, add plants until there are 2 in the market.*/
1568
+ let minPlantsToAdd = 0;
1569
+ if (G.players.length == 2 || G.players.length == 3) {
1570
+ minPlantsToAdd = 1;
1571
+ }
1572
+ else if (G.players.length == 4 || G.players.length == 5) {
1573
+ minPlantsToAdd = 2;
1574
+ }
1575
+ else if (G.players.length == 6) {
1576
+ minPlantsToAdd = 3;
1577
+ }
1578
+ const currentActualSize = G.actualMarket.length;
1579
+ const minSize = G.players.length - 1;
1580
+ const numPlantsToAdd = Math.max(minPlantsToAdd, minSize - currentActualSize);
1581
+ for (let i = 0; i < numPlantsToAdd; i++) {
1582
+ if (G.step == 3) {
1583
+ break;
1584
+ }
1585
+ else {
1586
+ addPowerPlant(G);
1587
+ }
1588
+ }
1589
+ // Special rule to move the market for two players
1590
+ while (G.players.length == 2 && G.actualMarket.length < 2 && G.step != 3) {
1591
+ addPowerPlant(G);
1592
+ }
1593
+ if (G.step == 3) {
1594
+ G.actualMarket = G.actualMarket.filter((pp) => pp.type != gamestate_1.PowerPlantType.Step3);
1595
+ while (G.actualMarket.length < 4 && G.powerPlantsDeck.length > 0) {
1596
+ addPowerPlant(G);
1597
+ }
1598
+ }
1599
+ else {
1600
+ // Target size is one less than number of players, or 2 for a 2-player game.
1601
+ const targetSize = Math.max(2, G.players.length - 1);
1602
+ while (G.actualMarket.length > targetSize) {
1603
+ G.actualMarket.shift();
1604
+ }
1605
+ }
1606
+ }
1607
+ function removePowerPlant(G, powerPlant) {
1608
+ G.actualMarket.splice(G.actualMarket.findIndex((pp) => pp.number == powerPlant.number), 1);
1609
+ }
1610
+ function getPowerPlant(num, mapName = '') {
1611
+ if (mapName == 'India') {
1612
+ return powerPlants_1.indiaPowerPlants.find((p) => p.number == num);
1613
+ }
1614
+ else {
1615
+ return powerPlants_1.powerPlants.find((p) => p.number == num);
1616
+ }
1617
+ }
1618
+ exports.getPowerPlant = getPowerPlant;
1619
+ function getBaseState(G) {
1620
+ const baseState = setup(G.players.length, G.options, G.seed);
1621
+ baseState.players.forEach((player, i) => {
1622
+ player.name = G.players[i].name;
1623
+ player.isAI = G.players[i].isAI;
1624
+ });
1625
+ return baseState;
1626
+ }
1627
+ function playerNameHTML(player) {
1628
+ return `<span style="background-color: ${exports.playerColors[player.id]}; font-weight: bold; padding: 0 3px;">${player.name}</span>`;
1629
+ }
1630
+ function playersSortedByScore(G) {
1631
+ return lodash_1.cloneDeep(G.players)
1632
+ .sort((p1, p2) => {
1633
+ if (p1.citiesPowered == p2.citiesPowered) {
1634
+ if (p1.money == p2.money) {
1635
+ return p1.cities.length - p2.cities.length;
1636
+ }
1637
+ return p1.money - p2.money;
1638
+ }
1639
+ return p1.citiesPowered - p2.citiesPowered;
1640
+ })
1641
+ .reverse();
1642
+ }
1643
+ exports.playersSortedByScore = playersSortedByScore;
1644
+ function calculateCitiesPowered(G) {
1645
+ G.players.forEach((player) => {
1646
+ player.citiesPowered = calculateMaxCitiesPowered(player);
1647
+ });
1648
+ }
1649
+ function calculateMaxCitiesPowered(player) {
1650
+ const permutations = [];
1651
+ for (let i = 0; i < Math.pow(2, player.powerPlants.length); i++) {
1652
+ const perm = [];
1653
+ player.powerPlants.forEach((pp, index) => {
1654
+ if (i & Math.pow(2, index)) {
1655
+ perm.push(pp);
1656
+ }
1657
+ });
1658
+ permutations.push(perm);
1659
+ }
1660
+ let max = 0;
1661
+ permutations.forEach((permutation) => {
1662
+ if (isValid(player, permutation)) {
1663
+ const citiesPowered = permutation.map((p) => p.citiesPowered).reduce((a, b) => a + b, 0);
1664
+ max = Math.max(max, citiesPowered);
1665
+ }
1666
+ });
1667
+ return Math.min(player.cities.length, max);
1668
+ }
1669
+ function isValid(player, powerPlants) {
1670
+ const coalUsed = powerPlants
1671
+ .filter((pp) => pp.type == gamestate_1.PowerPlantType.Coal)
1672
+ .map((pp) => pp.cost)
1673
+ .reduce((a, b) => a + b, 0);
1674
+ const oilUsed = powerPlants
1675
+ .filter((pp) => pp.type == gamestate_1.PowerPlantType.Oil)
1676
+ .map((pp) => pp.cost)
1677
+ .reduce((a, b) => a + b, 0);
1678
+ const garbageUsed = powerPlants
1679
+ .filter((pp) => pp.type == gamestate_1.PowerPlantType.Garbage)
1680
+ .map((pp) => pp.cost)
1681
+ .reduce((a, b) => a + b, 0);
1682
+ const uraniumUsed = powerPlants
1683
+ .filter((pp) => pp.type == gamestate_1.PowerPlantType.Uranium)
1684
+ .map((pp) => pp.cost)
1685
+ .reduce((a, b) => a + b, 0);
1686
+ const hybridUsed = powerPlants
1687
+ .filter((pp) => pp.type == gamestate_1.PowerPlantType.Hybrid)
1688
+ .map((pp) => pp.cost)
1689
+ .reduce((a, b) => a + b, 0);
1690
+ if (coalUsed > player.coalLeft ||
1691
+ oilUsed > player.oilLeft ||
1692
+ garbageUsed > player.garbageLeft ||
1693
+ uraniumUsed > player.uraniumLeft) {
1694
+ return false;
1695
+ }
1696
+ if (hybridUsed > player.coalLeft - coalUsed + player.oilLeft - oilUsed) {
1697
+ return false;
1698
+ }
1699
+ return true;
1700
+ }
1701
+ function toResourcesPhase(G) {
1702
+ G.players.forEach((p) => {
1703
+ p.bid = 0;
1704
+ p.passed = p.isDropped;
1705
+ });
1706
+ G.players.forEach((p) => {
1707
+ p.skipAuction = false;
1708
+ });
1709
+ if (G.options.variant == 'recharged') {
1710
+ if (G.plantDiscountActive) {
1711
+ G.plantDiscountActive = false;
1712
+ if (G.actualMarket.length > 0) {
1713
+ G.log.push({ type: 'event', event: `Discarding Power Plant ${G.actualMarket[0].number}.` });
1714
+ G.actualMarket.shift();
1715
+ addPowerPlant(G);
1716
+ }
1717
+ }
1718
+ else if (G.map.name == 'Benelux') {
1719
+ G.discardSmallestPlant = true;
1720
+ }
1721
+ }
1722
+ if (G.futureMarket.find((pp) => pp.number == 99)) {
1723
+ if (G.map.name == 'Middle East' && G.step == 1) {
1724
+ enterStepTwoMiddleEast(G);
1725
+ }
1726
+ else {
1727
+ const powerPlantDiscarded = G.actualMarket.shift();
1728
+ G.futureMarket.pop();
1729
+ G.log.push({
1730
+ type: 'event',
1731
+ event: `Starting Step 3, Power Plant ${powerPlantDiscarded === null || powerPlantDiscarded === void 0 ? void 0 : powerPlantDiscarded.number} discarded.`,
1732
+ });
1733
+ G.step = 3;
1734
+ G.actualMarket = [...G.actualMarket, ...G.futureMarket];
1735
+ G.futureMarket = [];
1736
+ }
1737
+ }
1738
+ G.phase = gamestate_1.Phase.Resources;
1739
+ setCurrentPlayer(G, G.playerOrder[G.players.length - 1]);
1740
+ }
1741
+ function endAuction(G, winningPlayer, bid) {
1742
+ winningPlayer.powerPlants.push(G.chosenPowerPlant);
1743
+ winningPlayer.money -= bid;
1744
+ if (G.options.trackTotalSpent) {
1745
+ winningPlayer.totalSpentPlants += bid;
1746
+ }
1747
+ winningPlayer.skipAuction = true;
1748
+ updatePlayerCapacity(winningPlayer);
1749
+ G.log.push({
1750
+ type: 'event',
1751
+ event: `${winningPlayer.name} wins the bid and pays ${bid}.`,
1752
+ pretty: `${playerNameHTML(winningPlayer)} wins the bid and pays <span style="color: green">$${bid}</span>.`,
1753
+ });
1754
+ removePowerPlant(G, G.chosenPowerPlant);
1755
+ G.chosenPowerPlant = G.currentBid = undefined;
1756
+ }
1757
+ function setPlayerOrder(G) {
1758
+ G.playerOrder = lodash_1.cloneDeep(G.players)
1759
+ .sort((a, b) => {
1760
+ const citiesA = a.cities.length;
1761
+ const citiesB = b.cities.length;
1762
+ if (citiesA == citiesB) {
1763
+ return (Math.max(...a.powerPlants.map((pp) => pp.number)) -
1764
+ Math.max(...b.powerPlants.map((pp) => pp.number)));
1765
+ }
1766
+ return citiesA - citiesB;
1767
+ })
1768
+ .map((p) => p.id)
1769
+ .reverse();
1770
+ }
1771
+ function setCurrentPlayer(G, playerNum) {
1772
+ G.currentPlayers = [playerNum];
1773
+ if (G.players[playerNum].isDropped && G.players.some((p) => !p.isDropped)) {
1774
+ G.players[playerNum].passed = true;
1775
+ nextPlayer(G);
1776
+ }
1777
+ }
1778
+ function nextPlayer(G) {
1779
+ if (G.phase == gamestate_1.Phase.Auction) {
1780
+ if (G.chosenPowerPlant == undefined) {
1781
+ nextPlayerAuction(G);
1782
+ }
1783
+ else {
1784
+ nextPlayerClockwise(G);
1785
+ }
1786
+ }
1787
+ else {
1788
+ nextPlayerReverse(G);
1789
+ }
1790
+ }
1791
+ function nextPlayerClockwise(G) {
1792
+ const index = G.currentPlayers[0];
1793
+ G.currentPlayers = [(index + 1) % G.players.length];
1794
+ if (G.players[G.currentPlayers[0]].isDropped && G.players.some((p) => !p.isDropped)) {
1795
+ G.players[G.currentPlayers[0]].passed = true;
1796
+ G.players[G.currentPlayers[0]].skipAuction = true;
1797
+ nextPlayerClockwise(G);
1798
+ }
1799
+ if ((G.players[G.currentPlayers[0]].skipAuction || G.players[G.currentPlayers[0]].passed) &&
1800
+ G.players.some((p) => !p.skipAuction && !p.passed && !p.isDropped)) {
1801
+ nextPlayerClockwise(G);
1802
+ }
1803
+ }
1804
+ function nextPlayerReverse(G) {
1805
+ const index = G.playerOrder.indexOf(G.currentPlayers[0]);
1806
+ G.currentPlayers = [G.playerOrder[(index - 1 + G.players.length) % G.players.length]];
1807
+ if (G.players[G.currentPlayers[0]].isDropped && G.players.some((p) => !p.isDropped)) {
1808
+ G.players[G.currentPlayers[0]].passed = true;
1809
+ nextPlayerReverse(G);
1810
+ }
1811
+ }
1812
+ function nextPlayerAuction(G, reset = false) {
1813
+ let playerNum;
1814
+ if (reset) {
1815
+ playerNum = G.playerOrder[0];
1816
+ }
1817
+ else {
1818
+ const index = G.playerOrder.indexOf(G.currentPlayers[0]);
1819
+ playerNum = G.playerOrder[(index + 1) % G.players.length];
1820
+ }
1821
+ G.currentPlayers = [playerNum];
1822
+ const player = G.players[playerNum];
1823
+ if (player.isDropped) {
1824
+ player.passed = true;
1825
+ player.skipAuction = true;
1826
+ }
1827
+ if ((player.skipAuction || player.passed) && G.players.some((p) => !p.skipAuction && !p.passed && !p.isDropped)) {
1828
+ nextPlayerAuction(G);
1829
+ }
1830
+ }
1831
+ function updateGameState(G) {
1832
+ if (!G.coalResupply) {
1833
+ const map = maps_1.maps.find((map) => map.name === G.map.name);
1834
+ if (map === null || map === void 0 ? void 0 : map.resupply) {
1835
+ G.coalResupply = map.resupply[0];
1836
+ G.oilResupply = map.resupply[1];
1837
+ G.garbageResupply = map.resupply[2];
1838
+ G.uraniumResupply = map.resupply[3];
1839
+ }
1840
+ else {
1841
+ G.coalResupply = [
1842
+ [3, 4, 3],
1843
+ [4, 5, 3],
1844
+ [5, 6, 4],
1845
+ [5, 7, 5],
1846
+ [7, 9, 6],
1847
+ ];
1848
+ G.oilResupply = [
1849
+ [2, 2, 4],
1850
+ [2, 3, 4],
1851
+ [3, 4, 5],
1852
+ [4, 5, 6],
1853
+ [5, 6, 7],
1854
+ ];
1855
+ G.garbageResupply = [
1856
+ [1, 2, 3],
1857
+ [1, 2, 3],
1858
+ [2, 3, 4],
1859
+ [3, 3, 5],
1860
+ [3, 5, 6],
1861
+ ];
1862
+ G.uraniumResupply = [
1863
+ [1, 1, 1],
1864
+ [1, 1, 1],
1865
+ [1, 2, 2],
1866
+ [2, 3, 2],
1867
+ [2, 3, 3],
1868
+ ];
1869
+ }
1870
+ const p = G.players.length - 2;
1871
+ G.resourceResupply = [
1872
+ `[${G.coalResupply[p][0]}, ${G.oilResupply[p][0]}, ${G.garbageResupply[p][0]}, ${G.uraniumResupply[p][0]}]`,
1873
+ `[${G.coalResupply[p][1]}, ${G.oilResupply[p][1]}, ${G.garbageResupply[p][1]}, ${G.uraniumResupply[p][1]}]`,
1874
+ `[${G.coalResupply[p][2]}, ${G.oilResupply[p][2]}, ${G.garbageResupply[p][2]}, ${G.uraniumResupply[p][2]}]`,
1875
+ ];
1876
+ }
1877
+ }
1878
+ function fastAuction(G, player, bid) {
1879
+ player.bid = bid;
1880
+ G.currentPlayers = G.currentPlayers.filter((id) => id !== player.id);
1881
+ if (G.currentPlayers.length === 0) {
1882
+ G.log.push(...G.hiddenLog);
1883
+ G.hiddenLog = [];
1884
+ const bids = G.players.map((p) => p.bid).filter((b) => b > 0);
1885
+ let cost = G.minimunBid;
1886
+ const highestBid = Math.max(...bids);
1887
+ const highestBidders = G.players.filter((p) => !p.isDropped && p.bid === highestBid);
1888
+ let winnerId = highestBidders[0].id;
1889
+ if (bids.length > 1) {
1890
+ bids.splice(bids.indexOf(highestBid), 1);
1891
+ const secondHighestBid = Math.max(...bids);
1892
+ // In case of a tie, use turn order
1893
+ if (highestBidders.length > 1) {
1894
+ let index = G.auctioningPlayer;
1895
+ while (!highestBidders.find((p) => p.id == index)) {
1896
+ index = (index + 1) % G.players.length;
1897
+ }
1898
+ cost = secondHighestBid;
1899
+ winnerId = index;
1900
+ }
1901
+ else {
1902
+ let index = G.auctioningPlayer;
1903
+ cost = 0;
1904
+ while (cost == 0) {
1905
+ if (highestBidders[0].id == index) {
1906
+ cost = secondHighestBid;
1907
+ }
1908
+ else if (G.players[index].bid == secondHighestBid) {
1909
+ cost = secondHighestBid + 1;
1910
+ }
1911
+ index = (index + 1) % G.players.length;
1912
+ }
1913
+ }
1914
+ }
1915
+ const winningPlayer = G.players[winnerId];
1916
+ endAuction(G, winningPlayer, cost);
1917
+ if ((winningPlayer.powerPlants.length > 4 || (G.players.length > 2 && winningPlayer.powerPlants.length > 3)) &&
1918
+ !winningPlayer.isDropped) {
1919
+ setCurrentPlayer(G, winningPlayer.id);
1920
+ }
1921
+ else {
1922
+ if (G.map.name != 'China' || G.step == 3) {
1923
+ addPowerPlant(G);
1924
+ }
1925
+ if (G.players.some((p) => !p.skipAuction && !p.isDropped)) {
1926
+ G.players.forEach((p) => {
1927
+ p.bid = 0;
1928
+ p.passed = p.isDropped;
1929
+ });
1930
+ nextPlayerAuction(G, true);
1931
+ }
1932
+ else {
1933
+ toResourcesPhase(G);
1934
+ }
1935
+ }
1936
+ }
1937
+ }