powergrid-engine 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/available-moves.d.ts +2 -0
- package/dist/src/available-moves.js +110 -9
- package/dist/src/engine.js +290 -57
- package/dist/src/gamestate.d.ts +13 -1
- package/dist/src/maps/europe.d.ts +62 -0
- package/dist/src/maps/europe.js +356 -0
- package/dist/src/maps/korea.js +107 -0
- package/dist/src/maps/northamerica.d.ts +62 -0
- package/dist/src/maps/northamerica.js +330 -0
- package/dist/src/maps/northerneurope.js +75 -0
- package/dist/src/maps/southafrica.d.ts +26 -26
- package/dist/src/maps/southafrica.js +239 -116
- package/dist/src/maps/ukireland.js +163 -40
- package/dist/src/maps.d.ts +16 -0
- package/dist/src/maps.js +19 -8
- package/dist/src/move.d.ts +2 -0
- package/package.json +1 -1
- package/src/available-moves.ts +158 -10
- package/src/engine.spec.ts +17 -0
- package/src/engine.ts +305 -52
- package/src/gamestate.ts +33 -6
- package/src/maps/europe.ts +391 -0
- package/src/maps/korea.ts +109 -0
- package/src/maps/northamerica.ts +360 -0
- package/src/maps/northerneurope.ts +76 -0
- package/src/maps/southafrica.ts +247 -116
- package/src/maps/ukireland.ts +170 -40
- package/src/maps.ts +53 -13
- package/src/move.ts +7 -0
package/dist/src/maps.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface City {
|
|
|
6
6
|
x: number;
|
|
7
7
|
y: number;
|
|
8
8
|
transregional?: boolean;
|
|
9
|
+
singleOccupancy?: boolean;
|
|
10
|
+
island?: string;
|
|
9
11
|
}
|
|
10
12
|
export interface Connection {
|
|
11
13
|
nodes: string[];
|
|
@@ -28,6 +30,7 @@ export interface GameMap {
|
|
|
28
30
|
powerPlantMarketPosition?: [number, number];
|
|
29
31
|
actualMarketWidth?: number;
|
|
30
32
|
mapPosition?: [number, number];
|
|
33
|
+
mapRotation?: number;
|
|
31
34
|
buttonsPosition?: [number, number];
|
|
32
35
|
playerBoardsPosition?: [number, number];
|
|
33
36
|
supplyPosition?: [number, number];
|
|
@@ -35,6 +38,11 @@ export interface GameMap {
|
|
|
35
38
|
resupply?: number[][][];
|
|
36
39
|
startingResources?: number[];
|
|
37
40
|
startingSupply?: number[];
|
|
41
|
+
resupplyNorth?: number[][][];
|
|
42
|
+
startingResourcesNorth?: number[];
|
|
43
|
+
coalPricesNorth?: number[];
|
|
44
|
+
oilPricesNorth?: number[];
|
|
45
|
+
garbagePricesNorth?: number[];
|
|
38
46
|
maxPriceAvailable?: number[];
|
|
39
47
|
coalPrices?: number[];
|
|
40
48
|
oilPrices?: number[];
|
|
@@ -45,7 +53,15 @@ export interface GameMap {
|
|
|
45
53
|
futureMarket: PowerPlant[];
|
|
46
54
|
powerPlantsDeck: PowerPlant[];
|
|
47
55
|
};
|
|
56
|
+
regionalPowerPlants?: Record<string, PowerPlant[]>;
|
|
57
|
+
crossIslandSurcharge?: number;
|
|
48
58
|
mapSpecificRules?: string;
|
|
59
|
+
devBackdrop?: {
|
|
60
|
+
src: string;
|
|
61
|
+
width: number;
|
|
62
|
+
height: number;
|
|
63
|
+
opacity?: number;
|
|
64
|
+
};
|
|
49
65
|
}
|
|
50
66
|
export declare const maps: GameMap[];
|
|
51
67
|
export declare const mapsRecharged: GameMap[];
|
package/dist/src/maps.js
CHANGED
|
@@ -8,14 +8,21 @@ const benelux_1 = require("./maps/benelux");
|
|
|
8
8
|
const brazil_1 = require("./maps/brazil");
|
|
9
9
|
const centraleurope_1 = require("./maps/centraleurope");
|
|
10
10
|
const china_1 = require("./maps/china");
|
|
11
|
+
const europe_1 = require("./maps/europe");
|
|
11
12
|
const france_1 = require("./maps/france");
|
|
12
13
|
const germany_1 = require("./maps/germany");
|
|
13
14
|
const indian_1 = require("./maps/indian");
|
|
14
15
|
const italy_1 = require("./maps/italy");
|
|
16
|
+
// import { map as japan } from './maps/japan';
|
|
17
|
+
const korea_1 = require("./maps/korea");
|
|
15
18
|
const middleeast_1 = require("./maps/middleeast");
|
|
19
|
+
const northamerica_1 = require("./maps/northamerica");
|
|
20
|
+
const northerneurope_1 = require("./maps/northerneurope");
|
|
16
21
|
const quebec_1 = require("./maps/quebec");
|
|
17
22
|
const russia_1 = require("./maps/russia");
|
|
23
|
+
const southafrica_1 = require("./maps/southafrica");
|
|
18
24
|
const spainportugal_1 = require("./maps/spainportugal");
|
|
25
|
+
const ukireland_1 = require("./maps/ukireland");
|
|
19
26
|
exports.maps = [
|
|
20
27
|
america_1.map,
|
|
21
28
|
germany_1.map,
|
|
@@ -31,12 +38,14 @@ exports.maps = [
|
|
|
31
38
|
russia_1.map,
|
|
32
39
|
centraleurope_1.map,
|
|
33
40
|
badenwurttemberg_1.map,
|
|
41
|
+
northerneurope_1.map,
|
|
42
|
+
korea_1.map,
|
|
43
|
+
europe_1.map,
|
|
44
|
+
northamerica_1.map,
|
|
45
|
+
southafrica_1.map,
|
|
46
|
+
ukireland_1.map,
|
|
34
47
|
// australia,
|
|
35
48
|
// japan,
|
|
36
|
-
// korea,
|
|
37
|
-
// northerneurope,
|
|
38
|
-
// southafrica,
|
|
39
|
-
// ukireland,
|
|
40
49
|
];
|
|
41
50
|
exports.mapsRecharged = [
|
|
42
51
|
america_1.mapRecharged,
|
|
@@ -53,11 +62,13 @@ exports.mapsRecharged = [
|
|
|
53
62
|
russia_1.map,
|
|
54
63
|
centraleurope_1.map,
|
|
55
64
|
badenwurttemberg_1.map,
|
|
65
|
+
northerneurope_1.map,
|
|
66
|
+
korea_1.map,
|
|
67
|
+
europe_1.map,
|
|
68
|
+
northamerica_1.map,
|
|
69
|
+
southafrica_1.map,
|
|
70
|
+
ukireland_1.map,
|
|
56
71
|
// australia,
|
|
57
72
|
// china,
|
|
58
73
|
// japan,
|
|
59
|
-
// korea,
|
|
60
|
-
// northerneurope,
|
|
61
|
-
// southafrica,
|
|
62
|
-
// ukireland,
|
|
63
74
|
];
|
package/dist/src/move.d.ts
CHANGED
package/package.json
CHANGED
package/src/available-moves.ts
CHANGED
|
@@ -11,6 +11,12 @@ export interface AvailableMoves {
|
|
|
11
11
|
[MoveName.BuyResource]?: {
|
|
12
12
|
resource: ResourceType;
|
|
13
13
|
price: number;
|
|
14
|
+
// Korea: which side's market this buy option draws from.
|
|
15
|
+
// Omitted on all other maps.
|
|
16
|
+
side?: 'north' | 'south';
|
|
17
|
+
// South Africa: $8 flat buy from the coal storage pool below the market.
|
|
18
|
+
// Distinct from the regular market buy (which can also be offered alongside).
|
|
19
|
+
fromStorage?: boolean;
|
|
14
20
|
}[];
|
|
15
21
|
[MoveName.Build]?: { name: string; price: number }[];
|
|
16
22
|
[MoveName.UsePowerPlant]?: {
|
|
@@ -88,6 +94,18 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
|
|
97
|
+
// No nuclear plants for Republic of Ireland (Green region) on UK&I.
|
|
98
|
+
// The restriction lifts as soon as the player has any non-Green city
|
|
99
|
+
// (Scotland/Wales/England/Northern Ireland = Brown/Yellow/Red/Pink/Orange).
|
|
100
|
+
if (G.map.name == 'UK & Ireland') {
|
|
101
|
+
const playerCities = player.cities.map(
|
|
102
|
+
(c) => G.map.cities.find((c_) => c_.name == c.name)!
|
|
103
|
+
);
|
|
104
|
+
if (playerCities.every((c) => c.region == 'green')) {
|
|
105
|
+
canBid = canBid.filter((p) => p.type != PowerPlantType.Uranium);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
91
109
|
if (canBid.length > 0) {
|
|
92
110
|
moves[MoveName.ChoosePowerPlant] = canBid.map((p) => p.number);
|
|
93
111
|
}
|
|
@@ -135,6 +153,19 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
135
153
|
}
|
|
136
154
|
}
|
|
137
155
|
|
|
156
|
+
// No nuclear plants for Republic of Ireland (Green region) on UK&I.
|
|
157
|
+
if (G.map.name == 'UK & Ireland') {
|
|
158
|
+
const playerCities = player.cities.map(
|
|
159
|
+
(c) => G.map.cities.find((c_) => c_.name == c.name)!
|
|
160
|
+
);
|
|
161
|
+
if (
|
|
162
|
+
playerCities.every((c) => c.region == 'green') &&
|
|
163
|
+
G.chosenPowerPlant.type == PowerPlantType.Uranium
|
|
164
|
+
) {
|
|
165
|
+
moves[MoveName.Bid] = undefined;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
138
169
|
if (G.options.fastBid) {
|
|
139
170
|
if (player.id != G.auctioningPlayer) {
|
|
140
171
|
moves[MoveName.Pass] = [true];
|
|
@@ -157,7 +188,7 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
157
188
|
break;
|
|
158
189
|
}
|
|
159
190
|
|
|
160
|
-
const toBuy: { resource: ResourceType }[] = [];
|
|
191
|
+
const toBuy: { resource: ResourceType; side?: 'north' | 'south'; fromStorage?: boolean }[] = [];
|
|
161
192
|
let maxPriceAvailable: number;
|
|
162
193
|
if (G.map.maxPriceAvailable) {
|
|
163
194
|
maxPriceAvailable = G.map.maxPriceAvailable[G.step - 1];
|
|
@@ -165,7 +196,13 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
165
196
|
maxPriceAvailable = 16;
|
|
166
197
|
}
|
|
167
198
|
|
|
168
|
-
|
|
199
|
+
// Korea: each side is offered separately, tagged with the side. The
|
|
200
|
+
// player's chosenSide locks them to one side once they make a buy.
|
|
201
|
+
const isKorea = G.coalResupplyNorth !== undefined;
|
|
202
|
+
const allowSouth = !isKorea || G.chosenSide !== 'north';
|
|
203
|
+
const allowNorth = isKorea && G.chosenSide !== 'south';
|
|
204
|
+
|
|
205
|
+
if (allowSouth && G.coalMarket > 0) {
|
|
169
206
|
const hybridCapacityUsed =
|
|
170
207
|
player.hybridCapacity > 0 ? Math.max(0, player.oilLeft - player.oilCapacity) : 0;
|
|
171
208
|
const coalPrices = G.coalPrices ?? prices[ResourceType.Coal];
|
|
@@ -176,9 +213,11 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
176
213
|
player.coalCapacity + player.hybridCapacity > hybridCapacityUsed + player.coalLeft &&
|
|
177
214
|
price <= maxPriceAvailable
|
|
178
215
|
) {
|
|
179
|
-
toBuy.push(
|
|
216
|
+
toBuy.push(
|
|
217
|
+
isKorea ? { resource: ResourceType.Coal, side: 'south' } : { resource: ResourceType.Coal }
|
|
218
|
+
);
|
|
180
219
|
}
|
|
181
|
-
} else {
|
|
220
|
+
} else if (allowSouth) {
|
|
182
221
|
if (G.options.variant == 'recharged' && G.map.name == 'USA' && G.coalSupply > 0) {
|
|
183
222
|
const hybridCapacityUsed =
|
|
184
223
|
player.hybridCapacity > 0 ? Math.max(0, player.oilLeft - player.oilCapacity) : 0;
|
|
@@ -191,7 +230,37 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
191
230
|
}
|
|
192
231
|
}
|
|
193
232
|
|
|
194
|
-
|
|
233
|
+
// South Africa: $8 flat coal from the storage pool below the market.
|
|
234
|
+
// Always available alongside the regular market option (not gated on
|
|
235
|
+
// market being empty), as long as there are cubes in storage.
|
|
236
|
+
if (allowSouth && G.coalStorage !== undefined && G.coalStorage > 0) {
|
|
237
|
+
const hybridCapacityUsed =
|
|
238
|
+
player.hybridCapacity > 0 ? Math.max(0, player.oilLeft - player.oilCapacity) : 0;
|
|
239
|
+
if (
|
|
240
|
+
player.money >= 8 &&
|
|
241
|
+
player.coalCapacity + player.hybridCapacity > hybridCapacityUsed + player.coalLeft &&
|
|
242
|
+
8 <= maxPriceAvailable
|
|
243
|
+
) {
|
|
244
|
+
toBuy.push({ resource: ResourceType.Coal, fromStorage: true });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (allowNorth && G.coalMarketNorth! > 0) {
|
|
249
|
+
const hybridCapacityUsed =
|
|
250
|
+
player.hybridCapacity > 0 ? Math.max(0, player.oilLeft - player.oilCapacity) : 0;
|
|
251
|
+
const coalPrices = G.coalPricesNorth!;
|
|
252
|
+
const price = coalPrices[coalPrices.length - G.coalMarketNorth!];
|
|
253
|
+
|
|
254
|
+
if (
|
|
255
|
+
player.money >= price &&
|
|
256
|
+
player.coalCapacity + player.hybridCapacity > hybridCapacityUsed + player.coalLeft &&
|
|
257
|
+
price <= maxPriceAvailable
|
|
258
|
+
) {
|
|
259
|
+
toBuy.push({ resource: ResourceType.Coal, side: 'north' });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (allowSouth && G.oilMarket > 0) {
|
|
195
264
|
const hybridCapacityUsed =
|
|
196
265
|
player.hybridCapacity > 0 ? Math.max(0, player.coalLeft - player.coalCapacity) : 0;
|
|
197
266
|
const oilPrices = G.oilPrices ?? prices[ResourceType.Oil];
|
|
@@ -202,11 +271,28 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
202
271
|
player.oilCapacity + player.hybridCapacity > hybridCapacityUsed + player.oilLeft &&
|
|
203
272
|
price <= maxPriceAvailable
|
|
204
273
|
) {
|
|
205
|
-
toBuy.push(
|
|
274
|
+
toBuy.push(
|
|
275
|
+
isKorea ? { resource: ResourceType.Oil, side: 'south' } : { resource: ResourceType.Oil }
|
|
276
|
+
);
|
|
206
277
|
}
|
|
207
278
|
}
|
|
208
279
|
|
|
209
|
-
if (G.
|
|
280
|
+
if (allowNorth && G.oilMarketNorth! > 0) {
|
|
281
|
+
const hybridCapacityUsed =
|
|
282
|
+
player.hybridCapacity > 0 ? Math.max(0, player.coalLeft - player.coalCapacity) : 0;
|
|
283
|
+
const oilPrices = G.oilPricesNorth!;
|
|
284
|
+
const price = oilPrices[oilPrices.length - G.oilMarketNorth!];
|
|
285
|
+
|
|
286
|
+
if (
|
|
287
|
+
player.money >= price &&
|
|
288
|
+
player.oilCapacity + player.hybridCapacity > hybridCapacityUsed + player.oilLeft &&
|
|
289
|
+
price <= maxPriceAvailable
|
|
290
|
+
) {
|
|
291
|
+
toBuy.push({ resource: ResourceType.Oil, side: 'north' });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (allowSouth && G.garbageMarket > 0) {
|
|
210
296
|
const garbagePrices = G.garbagePrices ?? prices[ResourceType.Garbage];
|
|
211
297
|
let price = garbagePrices[garbagePrices.length - G.garbageMarket];
|
|
212
298
|
|
|
@@ -223,11 +309,27 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
223
309
|
player.garbageCapacity > player.garbageLeft &&
|
|
224
310
|
price <= maxPriceAvailable
|
|
225
311
|
) {
|
|
226
|
-
toBuy.push(
|
|
312
|
+
toBuy.push(
|
|
313
|
+
isKorea ? { resource: ResourceType.Garbage, side: 'south' } : { resource: ResourceType.Garbage }
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (allowNorth && G.garbageMarketNorth! > 0) {
|
|
319
|
+
const garbagePrices = G.garbagePricesNorth!;
|
|
320
|
+
const price = garbagePrices[garbagePrices.length - G.garbageMarketNorth!];
|
|
321
|
+
|
|
322
|
+
if (
|
|
323
|
+
player.money >= price &&
|
|
324
|
+
player.garbageCapacity > player.garbageLeft &&
|
|
325
|
+
price <= maxPriceAvailable
|
|
326
|
+
) {
|
|
327
|
+
toBuy.push({ resource: ResourceType.Garbage, side: 'north' });
|
|
227
328
|
}
|
|
228
329
|
}
|
|
229
330
|
|
|
230
|
-
|
|
331
|
+
// Uranium is South only (or non-Korea maps).
|
|
332
|
+
if (allowSouth && G.uraniumMarket > 0) {
|
|
231
333
|
const uraniumPrices = G.uraniumPrices ?? prices[ResourceType.Uranium];
|
|
232
334
|
const price = uraniumPrices[uraniumPrices.length - G.uraniumMarket];
|
|
233
335
|
if (
|
|
@@ -235,7 +337,9 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
235
337
|
player.uraniumCapacity > player.uraniumLeft &&
|
|
236
338
|
price <= maxPriceAvailable
|
|
237
339
|
) {
|
|
238
|
-
toBuy.push(
|
|
340
|
+
toBuy.push(
|
|
341
|
+
isKorea ? { resource: ResourceType.Uranium, side: 'south' } : { resource: ResourceType.Uranium }
|
|
342
|
+
);
|
|
239
343
|
}
|
|
240
344
|
}
|
|
241
345
|
|
|
@@ -280,6 +384,50 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
280
384
|
return;
|
|
281
385
|
}
|
|
282
386
|
|
|
387
|
+
// UK & Ireland: starting a network on an island where the player has
|
|
388
|
+
// no city yet pays the first-house base + crossIslandSurcharge. There
|
|
389
|
+
// is no sea edge so dijkstra reports the target city as unreachable
|
|
390
|
+
// (price=9999); we override here. The first build ever (player.cities
|
|
391
|
+
// empty) goes through the normal first-build path and pays no surcharge.
|
|
392
|
+
if (cityData.island && G.map.crossIslandSurcharge !== undefined && player.cities.length > 0) {
|
|
393
|
+
const playerIslands = new Set(
|
|
394
|
+
player.cities
|
|
395
|
+
.map((c) => G.map.cities.find((mc) => mc.name == c.name)?.island)
|
|
396
|
+
.filter((i): i is string => !!i)
|
|
397
|
+
);
|
|
398
|
+
if (!playerIslands.has(cityData.island)) {
|
|
399
|
+
city.price = 10 + othersCount * 5 + G.map.crossIslandSurcharge;
|
|
400
|
+
|
|
401
|
+
if (othersCount == G.step) {
|
|
402
|
+
city.price = 9999;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (player.cities.find((c) => c.name == city.name)) {
|
|
406
|
+
city.price = 9999;
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// South Africa's cross-border foreign-country spaces: cap at 1 occupant
|
|
413
|
+
// ever, and the dijkstra path cost (30 via the cross-border edge) is the
|
|
414
|
+
// complete cost — no 10+position*5 house base is added. Players cannot
|
|
415
|
+
// start in one of these (you have to build INTO South Africa first).
|
|
416
|
+
if (cityData.singleOccupancy) {
|
|
417
|
+
if (player.cities.length == 0) {
|
|
418
|
+
city.price = 9999;
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (othersCount >= 1) {
|
|
422
|
+
city.price = 9999;
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (player.cities.find((c) => c.name == city.name)) {
|
|
426
|
+
city.price = 9999;
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
283
431
|
city.price += 10 + othersCount * 5;
|
|
284
432
|
|
|
285
433
|
if (othersCount == G.step) {
|
package/src/engine.spec.ts
CHANGED
|
@@ -141,6 +141,23 @@ describe('Engine', () => {
|
|
|
141
141
|
expect(ended(G)).to.be.false;
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
+
it('should place UK & Ireland Step 3 card third from last with two plants below it', () => {
|
|
145
|
+
// UK & Ireland rules: the Step 3 card (plant 99) goes at deck.length - 3
|
|
146
|
+
// so two plants sit below it, and Step 3 fires two auctions earlier than
|
|
147
|
+
// a standard "Step 3 at the bottom" deck.
|
|
148
|
+
const G = setup(5, { map: 'UK & Ireland', variant: 'recharged', randomizeMap: false }, 'ukireland-test-seed');
|
|
149
|
+
|
|
150
|
+
const step3Idx = G.powerPlantsDeck.findIndex((p) => p.number === 99);
|
|
151
|
+
expect(step3Idx).to.equal(G.powerPlantsDeck.length - 3);
|
|
152
|
+
|
|
153
|
+
// The two plants below Step 3 are real plants (not another Step 3 card or
|
|
154
|
+
// undefined slots).
|
|
155
|
+
expect(G.powerPlantsDeck[step3Idx + 1]).to.exist;
|
|
156
|
+
expect(G.powerPlantsDeck[step3Idx + 2]).to.exist;
|
|
157
|
+
expect(G.powerPlantsDeck[step3Idx + 1].number).to.not.equal(99);
|
|
158
|
+
expect(G.powerPlantsDeck[step3Idx + 2].number).to.not.equal(99);
|
|
159
|
+
});
|
|
160
|
+
|
|
144
161
|
it('should allow invalid move when isUndo is true', () => {
|
|
145
162
|
const game = undo;
|
|
146
163
|
const options: GameOptions = {
|