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,450 @@
1
+ import { range } from 'lodash';
2
+ import { GameState, Phase, Player, PowerPlantType, ResourceType } from './gamestate';
3
+ import { MoveName } from './move';
4
+ import prices from './prices';
5
+
6
+ export interface AvailableMoves {
7
+ [MoveName.ChoosePowerPlant]?: number[];
8
+ [MoveName.Bid]?: number[];
9
+ [MoveName.DiscardPowerPlant]?: number[];
10
+ [MoveName.DiscardResources]?: ResourceType[];
11
+ [MoveName.BuyResource]?: {
12
+ resource: ResourceType;
13
+ price: number;
14
+ }[];
15
+ [MoveName.Build]?: { name: string; price: number }[];
16
+ [MoveName.UsePowerPlant]?: {
17
+ powerPlant: number;
18
+ resourcesSpent: ResourceType[];
19
+ citiesPowered: number;
20
+ }[];
21
+ [MoveName.Pass]?: boolean[];
22
+ [MoveName.Undo]?: boolean[];
23
+ }
24
+
25
+ export function availableMoves(G: GameState, player: Player): AvailableMoves {
26
+ const moves = {};
27
+
28
+ const lastLog = G.log[G.log.length - 1];
29
+ if (lastLog.type == 'move' && G.currentPlayers.includes(player.id)) {
30
+ if (lastLog.player == player.id && player.lastMove?.name != MoveName.Pass) {
31
+ moves[MoveName.Undo] = [true, false];
32
+ } else if (G.phase == Phase.Bureaucracy && player.lastMove?.name == MoveName.UsePowerPlant) {
33
+ moves[MoveName.Undo] = [true, false];
34
+ }
35
+ }
36
+
37
+ switch (G.phase) {
38
+ case Phase.Auction: {
39
+ if (player.powerPlants.length > 4 || (G.players.length > 2 && player.powerPlants.length > 3)) {
40
+ moves[MoveName.DiscardPowerPlant] = player.powerPlants
41
+ .filter((_, i) => i != player.powerPlants.length - 1)
42
+ .map((pp) => pp.number);
43
+ } else {
44
+ const toDiscard: ResourceType[] = [];
45
+ let hybridCapacityUsed = Math.max(0, player.oilLeft - player.oilCapacity);
46
+ if (player.coalCapacity + player.hybridCapacity < player.coalLeft + hybridCapacityUsed) {
47
+ toDiscard.push(ResourceType.Coal);
48
+ }
49
+
50
+ hybridCapacityUsed = Math.max(0, player.coalLeft - player.coalCapacity);
51
+ if (player.oilCapacity + player.hybridCapacity < player.oilLeft + hybridCapacityUsed) {
52
+ toDiscard.push(ResourceType.Oil);
53
+ }
54
+
55
+ if (player.garbageCapacity < player.garbageLeft) {
56
+ toDiscard.push(ResourceType.Garbage);
57
+ }
58
+
59
+ if (player.uraniumCapacity < player.uraniumLeft) {
60
+ toDiscard.push(ResourceType.Uranium);
61
+ }
62
+
63
+ if (toDiscard.length > 0) {
64
+ moves[MoveName.DiscardResources] = toDiscard;
65
+ } else {
66
+ if (G.chosenPowerPlant == undefined) {
67
+ let canBid = G.actualMarket.filter((p) => player.money >= p.number);
68
+
69
+ // No nuclear plants for Portugal
70
+ if (G.map.name == 'Spain & Portugal') {
71
+ const playerCities = player.cities.map(
72
+ (c) => G.map.cities.find((c_) => c_.name == c.name)!
73
+ );
74
+ if (playerCities.every((c) => c.region == 'yellow')) {
75
+ canBid = canBid.filter((p) => p.type != PowerPlantType.Uranium);
76
+ }
77
+ }
78
+
79
+ // Nuclear plants for Central Europe are only allowed for players with cities in:
80
+ // Czechia (green), Slovakia (brown), Hungary (purple)
81
+ if (G.map.name == 'Central Europe') {
82
+ const validCities = player.cities
83
+ .map((c) => G.map.cities.find((c_) => c_.name == c.name)!)
84
+ .filter((c) => c.region == 'green' || c.region == 'brown' || c.region == 'purple');
85
+
86
+ if (validCities.length == 0) {
87
+ canBid = canBid.filter((p) => p.type != PowerPlantType.Uranium);
88
+ }
89
+ }
90
+
91
+ if (canBid.length > 0) {
92
+ moves[MoveName.ChoosePowerPlant] = canBid.map((p) => p.number);
93
+ }
94
+
95
+ if (G.round > 1) {
96
+ moves[MoveName.Pass] = [true];
97
+ }
98
+ } else {
99
+ if (G.options.fastBid) {
100
+ if (G.minimunBid <= player.money) {
101
+ moves[MoveName.Bid] = range(G.minimunBid, player.money + 1);
102
+ }
103
+ } else {
104
+ if (G.currentBid) {
105
+ if (G.currentBid < player.money) {
106
+ moves[MoveName.Bid] = range(G.currentBid + 1, player.money + 1);
107
+ }
108
+ } else {
109
+ moves[MoveName.Bid] = range(G.minimunBid, player.money + 1);
110
+ }
111
+ }
112
+
113
+ // No nuclear plants for Portugal
114
+ if (G.map.name == 'Spain & Portugal') {
115
+ const playerCities = player.cities.map(
116
+ (c) => G.map.cities.find((c_) => c_.name == c.name)!
117
+ );
118
+ if (
119
+ playerCities.every((c) => c.region == 'yellow') &&
120
+ G.chosenPowerPlant.type == PowerPlantType.Uranium
121
+ ) {
122
+ moves[MoveName.Bid] = undefined;
123
+ }
124
+ }
125
+
126
+ // Nuclear plants for Central Europe are only allowed for players with cities in:
127
+ // Czechia (green), Slovakia (brown), Hungary (purple)
128
+ if (G.map.name == 'Central Europe') {
129
+ const validCities = player.cities
130
+ .map((c) => G.map.cities.find((c_) => c_.name == c.name)!)
131
+ .filter((c) => c.region == 'green' || c.region == 'brown' || c.region == 'purple');
132
+
133
+ if (validCities.length == 0 && G.chosenPowerPlant.type == PowerPlantType.Uranium) {
134
+ moves[MoveName.Bid] = undefined;
135
+ }
136
+ }
137
+
138
+ if (G.options.fastBid) {
139
+ if (player.id != G.auctioningPlayer) {
140
+ moves[MoveName.Pass] = [true];
141
+ }
142
+ } else {
143
+ if (G.currentBid != null) {
144
+ moves[MoveName.Pass] = [true];
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ break;
152
+ }
153
+
154
+ case Phase.Resources: {
155
+ if (G.map.name == 'India' && G.chosenResource !== undefined) {
156
+ moves[MoveName.Pass] = [true];
157
+ break;
158
+ }
159
+
160
+ const toBuy: { resource: ResourceType }[] = [];
161
+ let maxPriceAvailable: number;
162
+ if (G.map.maxPriceAvailable) {
163
+ maxPriceAvailable = G.map.maxPriceAvailable[G.step - 1];
164
+ } else {
165
+ maxPriceAvailable = 16;
166
+ }
167
+
168
+ if (G.coalMarket > 0) {
169
+ const hybridCapacityUsed =
170
+ player.hybridCapacity > 0 ? Math.max(0, player.oilLeft - player.oilCapacity) : 0;
171
+ const coalPrices = G.coalPrices ?? prices[ResourceType.Coal];
172
+ const price = coalPrices[coalPrices.length - G.coalMarket];
173
+
174
+ if (
175
+ player.money >= price &&
176
+ player.coalCapacity + player.hybridCapacity > hybridCapacityUsed + player.coalLeft &&
177
+ price <= maxPriceAvailable
178
+ ) {
179
+ toBuy.push({ resource: ResourceType.Coal });
180
+ }
181
+ } else {
182
+ if (G.options.variant == 'recharged' && G.map.name == 'USA' && G.coalSupply > 0) {
183
+ const hybridCapacityUsed =
184
+ player.hybridCapacity > 0 ? Math.max(0, player.oilLeft - player.oilCapacity) : 0;
185
+ if (
186
+ player.money >= 8 &&
187
+ player.coalCapacity + player.hybridCapacity > hybridCapacityUsed + player.coalLeft
188
+ ) {
189
+ toBuy.push({ resource: ResourceType.Coal });
190
+ }
191
+ }
192
+ }
193
+
194
+ if (G.oilMarket > 0) {
195
+ const hybridCapacityUsed =
196
+ player.hybridCapacity > 0 ? Math.max(0, player.coalLeft - player.coalCapacity) : 0;
197
+ const oilPrices = G.oilPrices ?? prices[ResourceType.Oil];
198
+ const price = oilPrices[oilPrices.length - G.oilMarket];
199
+
200
+ if (
201
+ player.money >= price &&
202
+ player.oilCapacity + player.hybridCapacity > hybridCapacityUsed + player.oilLeft &&
203
+ price <= maxPriceAvailable
204
+ ) {
205
+ toBuy.push({ resource: ResourceType.Oil });
206
+ }
207
+ }
208
+
209
+ if (G.garbageMarket > 0) {
210
+ const garbagePrices = G.garbagePrices ?? prices[ResourceType.Garbage];
211
+ let price = garbagePrices[garbagePrices.length - G.garbageMarket];
212
+
213
+ // $1 cheaper for players in Wien in Central Europe
214
+ if (G.map.name == 'Central Europe') {
215
+ const wienCity = player.cities.filter((c) => c.name == 'Wien');
216
+ if (wienCity?.length > 0) {
217
+ price--;
218
+ }
219
+ }
220
+
221
+ if (
222
+ player.money >= price &&
223
+ player.garbageCapacity > player.garbageLeft &&
224
+ price <= maxPriceAvailable
225
+ ) {
226
+ toBuy.push({ resource: ResourceType.Garbage });
227
+ }
228
+ }
229
+
230
+ if (G.uraniumMarket > 0) {
231
+ const uraniumPrices = G.uraniumPrices ?? prices[ResourceType.Uranium];
232
+ const price = uraniumPrices[uraniumPrices.length - G.uraniumMarket];
233
+ if (
234
+ player.money >= price &&
235
+ player.uraniumCapacity > player.uraniumLeft &&
236
+ price <= maxPriceAvailable
237
+ ) {
238
+ toBuy.push({ resource: ResourceType.Uranium });
239
+ }
240
+ }
241
+
242
+ if (toBuy.length > 0) {
243
+ moves[MoveName.BuyResource] = toBuy;
244
+ }
245
+
246
+ moves[MoveName.Pass] = [true];
247
+
248
+ break;
249
+ }
250
+
251
+ case Phase.Building: {
252
+ if (player.cities.length < 21) {
253
+ let toBuild =
254
+ player.cities.length == 0
255
+ ? G.map.cities.map((c) => ({ name: c.name, price: 0 }))
256
+ : dijkstra(G, player).map((c) => ({ name: c.name, price: c.price }));
257
+
258
+ toBuild.forEach((city) => {
259
+ const othersCount = G.players.filter((p) => p.cities.find((c) => city.name == c.name)).length;
260
+ city.price += 10 + othersCount * 5;
261
+
262
+ if (othersCount == G.step) {
263
+ city.price = 9999;
264
+ }
265
+
266
+ if (player.cities.find((c) => c.name == city.name)) {
267
+ city.price = 9999;
268
+ }
269
+ });
270
+
271
+ toBuild = toBuild.filter((c) => c.price <= player.money);
272
+
273
+ if (toBuild.length > 0) {
274
+ moves[MoveName.Build] = toBuild;
275
+ }
276
+ }
277
+
278
+ moves[MoveName.Pass] = [true];
279
+
280
+ break;
281
+ }
282
+
283
+ case Phase.Bureaucracy: {
284
+ const toUse: { powerPlant: number; resourcesSpent: ResourceType[]; citiesPowered: number }[] = [];
285
+
286
+ player.powerPlants.forEach((powerPlant) => {
287
+ if (player.powerPlantsNotUsed.includes(powerPlant.number)) {
288
+ switch (powerPlant.type) {
289
+ case PowerPlantType.Coal: {
290
+ if (player.coalLeft >= powerPlant.cost) {
291
+ toUse.push({
292
+ powerPlant: powerPlant.number,
293
+ resourcesSpent: new Array(powerPlant.cost).fill(ResourceType.Coal),
294
+ citiesPowered: powerPlant.citiesPowered,
295
+ });
296
+ }
297
+
298
+ break;
299
+ }
300
+
301
+ case PowerPlantType.Oil: {
302
+ if (player.oilLeft >= powerPlant.cost) {
303
+ toUse.push({
304
+ powerPlant: powerPlant.number,
305
+ resourcesSpent: new Array(powerPlant.cost).fill(ResourceType.Oil),
306
+ citiesPowered: powerPlant.citiesPowered,
307
+ });
308
+ }
309
+
310
+ break;
311
+ }
312
+
313
+ case PowerPlantType.Garbage: {
314
+ if (player.garbageLeft >= powerPlant.cost) {
315
+ toUse.push({
316
+ powerPlant: powerPlant.number,
317
+ resourcesSpent: new Array(powerPlant.cost).fill(ResourceType.Garbage),
318
+ citiesPowered: powerPlant.citiesPowered,
319
+ });
320
+ }
321
+
322
+ break;
323
+ }
324
+
325
+ case PowerPlantType.Uranium: {
326
+ if (player.uraniumLeft >= powerPlant.cost) {
327
+ toUse.push({
328
+ powerPlant: powerPlant.number,
329
+ resourcesSpent: new Array(powerPlant.cost).fill(ResourceType.Uranium),
330
+ citiesPowered: powerPlant.citiesPowered,
331
+ });
332
+ }
333
+
334
+ break;
335
+ }
336
+
337
+ case PowerPlantType.Wind:
338
+ case PowerPlantType.Nuclear:
339
+ toUse.push({
340
+ powerPlant: powerPlant.number,
341
+ resourcesSpent: [],
342
+ citiesPowered: powerPlant.citiesPowered,
343
+ });
344
+
345
+ break;
346
+
347
+ case PowerPlantType.Hybrid: {
348
+ if (player.coalLeft + player.oilLeft >= powerPlant.cost) {
349
+ let resourcesSpentArr: ResourceType[][];
350
+ if (powerPlant.cost == 1) {
351
+ resourcesSpentArr = [[ResourceType.Coal], [ResourceType.Oil]];
352
+ } else if (powerPlant.cost == 2) {
353
+ resourcesSpentArr = [
354
+ [ResourceType.Coal, ResourceType.Coal],
355
+ [ResourceType.Coal, ResourceType.Oil],
356
+ [ResourceType.Oil, ResourceType.Oil],
357
+ ];
358
+ } else {
359
+ resourcesSpentArr = [
360
+ [ResourceType.Coal, ResourceType.Coal, ResourceType.Coal],
361
+ [ResourceType.Coal, ResourceType.Coal, ResourceType.Oil],
362
+ [ResourceType.Coal, ResourceType.Oil, ResourceType.Oil],
363
+ [ResourceType.Oil, ResourceType.Oil, ResourceType.Oil],
364
+ ];
365
+ }
366
+
367
+ resourcesSpentArr.forEach((resourcesSpent) => {
368
+ if (
369
+ resourcesSpent.filter((r) => r == ResourceType.Coal).length <=
370
+ player.coalLeft &&
371
+ resourcesSpent.filter((r) => r == ResourceType.Oil).length <= player.oilLeft
372
+ ) {
373
+ toUse.push({
374
+ powerPlant: powerPlant.number,
375
+ resourcesSpent: resourcesSpent,
376
+ citiesPowered: powerPlant.citiesPowered,
377
+ });
378
+ }
379
+ });
380
+ }
381
+ }
382
+ }
383
+ }
384
+ });
385
+
386
+ if (toUse.length > 0) {
387
+ moves[MoveName.UsePowerPlant] = toUse;
388
+ }
389
+
390
+ // For India map, players must power as many cities as possible.
391
+ if (G.map.name != 'India' || player.citiesPowered >= player.targetCitiesPowered! || player.isAI) {
392
+ moves[MoveName.Pass] = [true];
393
+ }
394
+ break;
395
+ }
396
+ }
397
+
398
+ return moves;
399
+ }
400
+
401
+ function dijkstra(G: GameState, player: Player): { name: string; price: number }[] {
402
+ const nodes = G.map.cities.map((c) => ({
403
+ name: c.name,
404
+ price: player.cities.find((city) => city.name == c.name) ? 0 : 9999,
405
+ visited: false,
406
+ }));
407
+
408
+ let currentNode = nodes.find((n) => n.name == player.cities[0].name)!;
409
+ currentNode.price = 0;
410
+
411
+ while (nodes.some((n) => !n.visited)) {
412
+ const currentConnections = G.map.connections.filter((c) => c.nodes.includes(currentNode.name));
413
+ currentConnections.forEach((connection) => {
414
+ const otherName = connection.nodes.filter((n) => n != currentNode.name)[0];
415
+ const otherNode = nodes.find((n) => n.name == otherName)!;
416
+ const price = player.cities.find((c) => c.name == otherNode.name) ? 0 : currentNode.price + connection.cost;
417
+ if (!otherNode.visited && otherNode.price > price) {
418
+ otherNode.price = price;
419
+ }
420
+ });
421
+
422
+ currentNode.visited = true;
423
+
424
+ if (!nodes.some((n) => !n.visited)) {
425
+ break;
426
+ }
427
+
428
+ currentNode = nodes.reduce((a, b) => {
429
+ if (a.visited) {
430
+ return b;
431
+ }
432
+
433
+ if (b.visited) {
434
+ return a;
435
+ }
436
+
437
+ if (a.price <= b.price) {
438
+ return a;
439
+ }
440
+
441
+ return b;
442
+ });
443
+
444
+ if (currentNode.price > player.money) {
445
+ break;
446
+ }
447
+ }
448
+
449
+ return nodes;
450
+ }
@@ -0,0 +1,163 @@
1
+ import { expect } from 'chai';
2
+ import 'mocha';
3
+ import { ended, move, reconstructState, setup } from './engine';
4
+ import GermanyRecharged from './fixtures/GermanyRecharged.json';
5
+ import supply from './fixtures/supply.json';
6
+ import undo from './fixtures/undo.json';
7
+ import USAOriginal from './fixtures/USAOriginal.json';
8
+ import { GameOptions, MapName, Variant } from './gamestate';
9
+ import { Move } from './move';
10
+
11
+ describe('Engine', () => {
12
+ it('should setup a game correctly', () => {
13
+ const G = setup(5, { fastBid: false }, 'test');
14
+
15
+ expect(ended(G)).to.false;
16
+ });
17
+
18
+ it('should play full game Germany recharged', () => {
19
+ const game = GermanyRecharged;
20
+ const options: GameOptions = {
21
+ fastBid: game.options.fastBid,
22
+ map: game.options.map as MapName,
23
+ showMoney: game.options.showMoney,
24
+ variant: game.options.variant as Variant,
25
+ useNewRechargedSetup: game.options.useNewRechargedSetup,
26
+ };
27
+
28
+ expect(options.useNewRechargedSetup).to.be.false;
29
+
30
+ let G = setup(game.players.length, options, game.seed);
31
+
32
+ for (const item of game.log) {
33
+ if (item.type === 'move') {
34
+ G = move(G, item.move! as Move, item.player!);
35
+ }
36
+ }
37
+
38
+ expect(G.currentPlayers).to.deep.equal([]);
39
+ expect(ended(G)).to.be.true;
40
+ });
41
+
42
+ it('should replay game Germany recharged', () => {
43
+ const game = GermanyRecharged;
44
+
45
+ expect(game.options.useNewRechargedSetup).to.be.false;
46
+
47
+ const G = reconstructState(game as any, game.log.length - 1);
48
+
49
+ expect(ended(G)).to.be.true;
50
+ });
51
+
52
+ it('should replay game without seed Germany recharged', () => {
53
+ const game = GermanyRecharged;
54
+ const options: GameOptions = {
55
+ fastBid: game.options.fastBid,
56
+ map: game.options.map as MapName,
57
+ showMoney: game.options.showMoney,
58
+ variant: game.options.variant as Variant,
59
+ useNewRechargedSetup: game.options.useNewRechargedSetup,
60
+ };
61
+
62
+ expect(options.useNewRechargedSetup).to.be.false;
63
+
64
+ let G = setup(game.players.length, options, game.seed);
65
+
66
+ for (const item of game.log) {
67
+ if (item.type === 'move') {
68
+ G = move(G, item.move! as Move, item.player!);
69
+ }
70
+ }
71
+
72
+ expect(G.currentPlayers).to.deep.equal([]);
73
+ expect(ended(G)).to.be.true;
74
+
75
+ G.seed = 'secret';
76
+ G = reconstructState(G, G.log.length - 2);
77
+
78
+ expect(ended(G)).to.be.false;
79
+ });
80
+
81
+ it('should play full game USA original', () => {
82
+ const game = USAOriginal;
83
+ const options: GameOptions = {
84
+ fastBid: game.options.fastBid,
85
+ map: game.options.map as MapName,
86
+ showMoney: game.options.showMoney,
87
+ variant: game.options.variant as Variant,
88
+ };
89
+
90
+ let G = setup(game.players.length, options, game.seed);
91
+
92
+ for (const item of game.log) {
93
+ if (item.type === 'move') {
94
+ G = move(G, item.move! as Move, item.player!);
95
+ }
96
+ }
97
+
98
+ expect(G.currentPlayers).to.deep.equal([]);
99
+ expect(ended(G)).to.be.true;
100
+ });
101
+
102
+ it('should replay game USA original', () => {
103
+ const game = USAOriginal;
104
+
105
+ const G = reconstructState(game as any, game.log.length - 1);
106
+
107
+ expect(ended(G)).to.be.true;
108
+ });
109
+
110
+ it('should replay game without seed USA original', () => {
111
+ const game = USAOriginal;
112
+ const options: GameOptions = {
113
+ fastBid: game.options.fastBid,
114
+ map: game.options.map as MapName,
115
+ showMoney: game.options.showMoney,
116
+ variant: game.options.variant as Variant,
117
+ };
118
+
119
+ let G = setup(game.players.length, options, game.seed);
120
+
121
+ for (const item of game.log) {
122
+ if (item.type === 'move') {
123
+ G = move(G, item.move! as Move, item.player!);
124
+ }
125
+ }
126
+
127
+ expect(G.currentPlayers).to.deep.equal([]);
128
+ expect(ended(G)).to.be.true;
129
+
130
+ G.seed = 'secret';
131
+ G = reconstructState(G, G.log.length - 2);
132
+
133
+ expect(ended(G)).to.be.false;
134
+ });
135
+
136
+ it('should replay game supply', () => {
137
+ const game = supply;
138
+
139
+ const G = reconstructState(game as any, game.log.length - 1);
140
+
141
+ expect(ended(G)).to.be.false;
142
+ });
143
+
144
+ it('should allow invalid move when isUndo is true', () => {
145
+ const game = undo;
146
+ const options: GameOptions = {
147
+ fastBid: game.options.fastBid,
148
+ map: game.options.map as MapName,
149
+ showMoney: game.options.showMoney,
150
+ variant: game.options.variant as Variant,
151
+ };
152
+
153
+ let G = setup(game.players.length, options, game.seed, game.knownPowerPlantDeck, game.map);
154
+
155
+ for (const item of game.log) {
156
+ if (item.type === 'move') {
157
+ G = move(G, item.move! as Move, item.player!, true);
158
+ }
159
+ }
160
+
161
+ expect(ended(G)).to.be.false;
162
+ });
163
+ });