powergrid-engine 1.12.1 → 1.13.1
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.js +64 -6
- package/dist/src/engine.js +22 -23
- package/dist/src/gamestate.d.ts +1 -1
- package/dist/src/maps/badenwurttemberg.js +45 -5
- package/dist/src/maps/japan.d.ts +1 -0
- package/dist/src/maps/japan.js +71 -18
- package/dist/src/maps/northerneurope.js +1 -1
- package/dist/src/maps.d.ts +3 -0
- package/dist/src/maps.js +3 -3
- package/package.json +1 -1
- package/src/available-moves.ts +66 -6
- package/src/engine.spec.ts +44 -0
- package/src/engine.ts +23 -24
- package/src/gamestate.ts +2 -2
- package/src/maps/badenwurttemberg.ts +45 -5
- package/src/maps/japan.ts +72 -17
- package/src/maps/northerneurope.ts +1 -1
- package/src/maps.ts +13 -3
|
@@ -9,7 +9,7 @@ const gamestate_1 = require("./gamestate");
|
|
|
9
9
|
const move_1 = require("./move");
|
|
10
10
|
const prices_1 = __importDefault(require("./prices"));
|
|
11
11
|
function availableMoves(G, player) {
|
|
12
|
-
var _a, _b, _c, _d, _e, _f;
|
|
12
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
13
13
|
const moves = {};
|
|
14
14
|
const lastLog = G.log[G.log.length - 1];
|
|
15
15
|
if (lastLog.type == 'move' && G.currentPlayers.includes(player.id)) {
|
|
@@ -260,9 +260,36 @@ function availableMoves(G, player) {
|
|
|
260
260
|
}
|
|
261
261
|
case gamestate_1.Phase.Building: {
|
|
262
262
|
if (player.cities.length < 21) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
const isJapan = G.map.name === 'Japan';
|
|
264
|
+
const japanStartingCities = isJapan ? new Set((_g = G.map.startingCities) !== null && _g !== void 0 ? _g : []) : new Set();
|
|
265
|
+
let toBuild;
|
|
266
|
+
if (player.cities.length == 0) {
|
|
267
|
+
// Japan: first house must go in one of the 6 designated starting cities.
|
|
268
|
+
const candidates = isJapan
|
|
269
|
+
? G.map.cities.filter((c) => japanStartingCities.has(c.name))
|
|
270
|
+
: G.map.cities;
|
|
271
|
+
toBuild = candidates.map((c) => ({ name: c.name, price: 0 }));
|
|
272
|
+
}
|
|
273
|
+
else if (isJapan && G.round === 1) {
|
|
274
|
+
// Japan round 1: any additional house must also be a starting city.
|
|
275
|
+
// Players cannot extend to adjacent non-starting cities until round 2.
|
|
276
|
+
toBuild = G.map.cities
|
|
277
|
+
.filter((c) => japanStartingCities.has(c.name))
|
|
278
|
+
.map((c) => ({ name: c.name, price: 0 }));
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
toBuild = dijkstra(G, player).map((c) => ({ name: c.name, price: c.price }));
|
|
282
|
+
// Japan: a player may start a second disconnected network at any available
|
|
283
|
+
// starting city, paying only the slot cost (no connection fee).
|
|
284
|
+
if (isJapan &&
|
|
285
|
+
countNetworks(G.map.connections, player.cities.map((c) => c.name)) < 2) {
|
|
286
|
+
toBuild.forEach((city) => {
|
|
287
|
+
if (japanStartingCities.has(city.name)) {
|
|
288
|
+
city.price = 0;
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
266
293
|
toBuild.forEach((city) => {
|
|
267
294
|
const cityData = G.map.cities.find((c) => c.name == city.name);
|
|
268
295
|
const othersCount = G.players.filter((p) => p.cities.find((c) => city.name == c.name)).length;
|
|
@@ -322,10 +349,15 @@ function availableMoves(G, player) {
|
|
|
322
349
|
}
|
|
323
350
|
return;
|
|
324
351
|
}
|
|
325
|
-
|
|
326
|
-
|
|
352
|
+
const slotCosts = cityData.slotCosts;
|
|
353
|
+
const maxSlotsThisStep = cityData.stepSlots ? cityData.stepSlots[G.step - 1] : G.step;
|
|
354
|
+
const totalSlots = slotCosts ? slotCosts.length : 3;
|
|
355
|
+
if (othersCount >= maxSlotsThisStep || othersCount >= totalSlots) {
|
|
327
356
|
city.price = 9999;
|
|
328
357
|
}
|
|
358
|
+
else {
|
|
359
|
+
city.price += slotCosts ? slotCosts[othersCount] : 10 + othersCount * 5;
|
|
360
|
+
}
|
|
329
361
|
if (player.cities.find((c) => c.name == city.name)) {
|
|
330
362
|
city.price = 9999;
|
|
331
363
|
}
|
|
@@ -441,6 +473,32 @@ function availableMoves(G, player) {
|
|
|
441
473
|
return moves;
|
|
442
474
|
}
|
|
443
475
|
exports.availableMoves = availableMoves;
|
|
476
|
+
function countNetworks(connections, cityNames) {
|
|
477
|
+
const citySet = new Set(cityNames);
|
|
478
|
+
const visited = new Set();
|
|
479
|
+
let count = 0;
|
|
480
|
+
for (const city of cityNames) {
|
|
481
|
+
if (visited.has(city))
|
|
482
|
+
continue;
|
|
483
|
+
count++;
|
|
484
|
+
const stack = [city];
|
|
485
|
+
while (stack.length > 0) {
|
|
486
|
+
const current = stack.pop();
|
|
487
|
+
if (visited.has(current))
|
|
488
|
+
continue;
|
|
489
|
+
visited.add(current);
|
|
490
|
+
for (const conn of connections) {
|
|
491
|
+
if (conn.nodes.includes(current)) {
|
|
492
|
+
const neighbor = conn.nodes.find((n) => n !== current);
|
|
493
|
+
if (citySet.has(neighbor) && !visited.has(neighbor)) {
|
|
494
|
+
stack.push(neighbor);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return count;
|
|
501
|
+
}
|
|
444
502
|
function dijkstra(G, player) {
|
|
445
503
|
const nodes = G.map.cities.map((c) => ({
|
|
446
504
|
name: c.name,
|
package/dist/src/engine.js
CHANGED
|
@@ -18,7 +18,7 @@ const utils_1 = require("./utils");
|
|
|
18
18
|
exports.playerColors = ['limegreen', 'mediumorchid', 'red', 'dodgerblue', 'yellow', 'brown'];
|
|
19
19
|
const citiesToStep2 = [10, 7, 7, 7, 6];
|
|
20
20
|
const citiesToStep2BadenWurttemberg = [9, 6, 6, 6, 5];
|
|
21
|
-
const citiesToStep2UKIreland = [
|
|
21
|
+
const citiesToStep2UKIreland = [10, 7, 7, 7, 6];
|
|
22
22
|
const citiesToEndGame = [21, 17, 17, 15, 14];
|
|
23
23
|
const cityIncome = [10, 22, 33, 44, 54, 64, 73, 82, 90, 98, 105, 112, 118, 124, 129, 134, 138, 142, 145, 148, 150, 150];
|
|
24
24
|
const regionsInPlay = [3, 3, 4, 5, 5];
|
|
@@ -531,31 +531,30 @@ function move(G, move, playerNumber, isUndo = false) {
|
|
|
531
531
|
nextPlayerAuction(G);
|
|
532
532
|
}
|
|
533
533
|
else {
|
|
534
|
-
if (G.auctionSkips == G.players.length &&
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const removed = [];
|
|
541
|
-
for (let i = 0; i < 2 && G.actualMarket.length > 0; i++) {
|
|
542
|
-
removed.push(G.actualMarket[0].number);
|
|
543
|
-
G.actualMarket.shift();
|
|
544
|
-
addPowerPlant(G);
|
|
545
|
-
}
|
|
546
|
-
G.log.push({
|
|
547
|
-
type: 'event',
|
|
548
|
-
event: `Everyone passed, removing the two lowest numbered Power Plants (${removed.join(', ')}).`,
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
else {
|
|
552
|
-
G.log.push({
|
|
553
|
-
type: 'event',
|
|
554
|
-
event: `Everyone passed, removing lowest numbered Power Plant (${G.actualMarket[0].number}).`,
|
|
555
|
-
});
|
|
534
|
+
if (G.auctionSkips == G.players.length && G.map.name == 'Baden-Württemberg') {
|
|
535
|
+
// Baden-Württemberg: remove the two lowest plants when no one buys.
|
|
536
|
+
// This applies regardless of variant.
|
|
537
|
+
const removed = [];
|
|
538
|
+
for (let i = 0; i < 2 && G.actualMarket.length > 0; i++) {
|
|
539
|
+
removed.push(G.actualMarket[0].number);
|
|
556
540
|
G.actualMarket.shift();
|
|
557
541
|
addPowerPlant(G);
|
|
558
542
|
}
|
|
543
|
+
G.log.push({
|
|
544
|
+
type: 'event',
|
|
545
|
+
event: `Everyone passed, removing the two lowest numbered Power Plants (${removed.join(', ')}).`,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
else if (G.auctionSkips == G.players.length &&
|
|
549
|
+
G.options.variant == 'original' &&
|
|
550
|
+
G.map.name != 'China' &&
|
|
551
|
+
G.map.name != 'Russia') {
|
|
552
|
+
G.log.push({
|
|
553
|
+
type: 'event',
|
|
554
|
+
event: `Everyone passed, removing lowest numbered Power Plant (${G.actualMarket[0].number}).`,
|
|
555
|
+
});
|
|
556
|
+
G.actualMarket.shift();
|
|
557
|
+
addPowerPlant(G);
|
|
559
558
|
}
|
|
560
559
|
toResourcesPhase(G);
|
|
561
560
|
}
|
package/dist/src/gamestate.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { AvailableMoves } from './available-moves';
|
|
|
2
2
|
import { LogItem } from './log';
|
|
3
3
|
import { GameMap } from './maps';
|
|
4
4
|
import { Move } from './move';
|
|
5
|
-
export declare type MapName = 'USA' | 'Germany' | 'Brazil' | 'Spain & Portugal' | 'France' | 'Italy' | 'Quebec' | 'Middle East' | 'India' | 'China' | 'Benelux' | 'Russia' | 'Central Europe' | 'Baden-Württemberg' | 'Northern Europe' | 'Korea' | 'Europe' | 'North America' | 'South Africa' | 'UK & Ireland';
|
|
5
|
+
export declare type MapName = 'USA' | 'Germany' | 'Brazil' | 'Spain & Portugal' | 'France' | 'Italy' | 'Quebec' | 'Middle East' | 'India' | 'China' | 'Benelux' | 'Russia' | 'Central Europe' | 'Baden-Württemberg' | 'Northern Europe' | 'Korea' | 'Europe' | 'North America' | 'South Africa' | 'UK & Ireland' | 'Japan';
|
|
6
6
|
export declare type Variant = 'original' | 'recharged';
|
|
7
7
|
export interface GameOptions {
|
|
8
8
|
fastBid?: boolean;
|
|
@@ -56,8 +56,24 @@ exports.map = {
|
|
|
56
56
|
{ name: Cities.Sigmarincen, region: Regions.Purple, x: 1111, y: 1803 },
|
|
57
57
|
{ name: Cities.Konstanz, region: Regions.Purple, x: 1088, y: 2163 },
|
|
58
58
|
{ name: Cities.Ulm, region: Regions.Purple, x: 1541, y: 1499 },
|
|
59
|
-
{
|
|
60
|
-
|
|
59
|
+
{
|
|
60
|
+
name: Cities.Augsburg,
|
|
61
|
+
region: Regions.Purple,
|
|
62
|
+
x: 1830,
|
|
63
|
+
y: 1505,
|
|
64
|
+
transregional: true,
|
|
65
|
+
slotCosts: [15, 20],
|
|
66
|
+
stepSlots: [0, 1, 2],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: Cities.Basel,
|
|
70
|
+
region: Regions.Green,
|
|
71
|
+
x: 103,
|
|
72
|
+
y: 2271,
|
|
73
|
+
transregional: true,
|
|
74
|
+
slotCosts: [15, 20],
|
|
75
|
+
stepSlots: [0, 1, 2],
|
|
76
|
+
},
|
|
61
77
|
{ name: Cities.Waldshuttiencen, region: Regions.Green, x: 502, y: 2166 },
|
|
62
78
|
{ name: Cities.Singen, region: Regions.Green, x: 874, y: 2084 },
|
|
63
79
|
{ name: Cities.Tuttlincen, region: Regions.Green, x: 870, y: 1854 },
|
|
@@ -70,8 +86,24 @@ exports.map = {
|
|
|
70
86
|
{ name: Cities.Sinsheim, region: Regions.Red, x: 920, y: 785 },
|
|
71
87
|
{ name: Cities.Heidelber, region: Regions.Red, x: 817, y: 660 },
|
|
72
88
|
{ name: Cities.Mannheim, region: Regions.Red, x: 710, y: 540 },
|
|
73
|
-
{
|
|
74
|
-
|
|
89
|
+
{
|
|
90
|
+
name: Cities.Luowigshafen,
|
|
91
|
+
region: Regions.Red,
|
|
92
|
+
x: 579,
|
|
93
|
+
y: 588,
|
|
94
|
+
transregional: true,
|
|
95
|
+
slotCosts: [15, 20],
|
|
96
|
+
stepSlots: [0, 1, 2],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: Cities.Nurnberg,
|
|
100
|
+
region: Regions.Blue,
|
|
101
|
+
x: 1837,
|
|
102
|
+
y: 771,
|
|
103
|
+
transregional: true,
|
|
104
|
+
slotCosts: [15, 20],
|
|
105
|
+
stepSlots: [0, 1, 2],
|
|
106
|
+
},
|
|
75
107
|
{ name: Cities.Ellwangen, region: Regions.Blue, x: 1637, y: 1030 },
|
|
76
108
|
{ name: Cities.Coppingen, region: Regions.Blue, x: 1387, y: 1233 },
|
|
77
109
|
{ name: Cities.Reutlincen, region: Regions.Blue, x: 1107, y: 1443 },
|
|
@@ -81,7 +113,15 @@ exports.map = {
|
|
|
81
113
|
{ name: Cities.Laha, region: Regions.Yellow, x: 296, y: 1575 },
|
|
82
114
|
{ name: Cities.Badenbaden, region: Regions.Yellow, x: 554, y: 1259 },
|
|
83
115
|
{ name: Cities.Offenburg, region: Regions.Yellow, x: 395, y: 1422 },
|
|
84
|
-
{
|
|
116
|
+
{
|
|
117
|
+
name: Cities.Strasbourg,
|
|
118
|
+
region: Regions.Yellow,
|
|
119
|
+
x: 211,
|
|
120
|
+
y: 1330,
|
|
121
|
+
transregional: true,
|
|
122
|
+
slotCosts: [15, 20],
|
|
123
|
+
stepSlots: [0, 1, 2],
|
|
124
|
+
},
|
|
85
125
|
{ name: Cities.Pforzheim, region: Regions.Yellow, x: 794, y: 1095 },
|
|
86
126
|
{ name: Cities.Rastatt, region: Regions.Yellow, x: 509, y: 1109 },
|
|
87
127
|
{ name: Cities.Karlsruhf, region: Regions.Yellow, x: 638, y: 974 },
|
package/dist/src/maps/japan.d.ts
CHANGED
package/dist/src/maps/japan.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.map = exports.Cities = exports.Regions = void 0;
|
|
3
|
+
exports.mapRecharged = exports.map = exports.Cities = exports.Regions = void 0;
|
|
4
4
|
var Regions;
|
|
5
5
|
(function (Regions) {
|
|
6
6
|
Regions["Brown"] = "brown";
|
|
@@ -50,42 +50,94 @@ var Cities;
|
|
|
50
50
|
exports.map = {
|
|
51
51
|
name: 'Japan',
|
|
52
52
|
cities: [
|
|
53
|
-
{ name: Cities.Asahikawa, region: Regions.Brown, x: 3384, y:
|
|
54
|
-
{ name: Cities.Sapporo, region: Regions.Brown, x: 3028, y:
|
|
55
|
-
{ name: Cities.Hakodate, region: Regions.Brown, x: 2622, y:
|
|
53
|
+
{ name: Cities.Asahikawa, region: Regions.Brown, x: 3384, y: 1014 },
|
|
54
|
+
{ name: Cities.Sapporo, region: Regions.Brown, x: 3028, y: 982, slotCosts: [10, 10, 20], stepSlots: [2, 2, 3] },
|
|
55
|
+
{ name: Cities.Hakodate, region: Regions.Brown, x: 2622, y: 1112 },
|
|
56
56
|
{ name: Cities.Admori, region: Regions.Brown, x: 3818, y: 1336 },
|
|
57
|
-
{ name: Cities.Morioka, region: Regions.Brown, x: 3636, y: 1646 },
|
|
57
|
+
{ name: Cities.Morioka, region: Regions.Brown, x: 3636, y: 1646, slotCosts: [15, 20], stepSlots: [0, 1, 2] },
|
|
58
58
|
{ name: Cities.Akita, region: Regions.Brown, x: 3472, y: 1480 },
|
|
59
59
|
{ name: Cities.Sendai, region: Regions.Brown, x: 3408, y: 1886 },
|
|
60
60
|
{ name: Cities.Niigata, region: Regions.Green, x: 2874, y: 1628 },
|
|
61
|
-
{ name: Cities.Koriyama, region: Regions.Green, x: 3086, y: 1932 },
|
|
61
|
+
{ name: Cities.Koriyama, region: Regions.Green, x: 3086, y: 1932, slotCosts: [10, 15] },
|
|
62
62
|
{ name: Cities.Nagano, region: Regions.Green, x: 2486, y: 1722 },
|
|
63
|
-
{ name: Cities.Saitama, region: Regions.Green, x: 2450, y: 1986 },
|
|
64
|
-
{ name: Cities.Tokyo, region: Regions.Green, x: 2524, y: 2122 },
|
|
63
|
+
{ name: Cities.Saitama, region: Regions.Green, x: 2450, y: 1986, slotCosts: [15, 20], stepSlots: [0, 1, 2] },
|
|
64
|
+
{ name: Cities.Tokyo, region: Regions.Green, x: 2524, y: 2122, slotCosts: [10, 10, 20], stepSlots: [2, 2, 3] },
|
|
65
65
|
{ name: Cities.Chiba, region: Regions.Green, x: 2650, y: 2316 },
|
|
66
|
-
{
|
|
66
|
+
{
|
|
67
|
+
name: Cities.Yokohama,
|
|
68
|
+
region: Regions.Green,
|
|
69
|
+
x: 2306,
|
|
70
|
+
y: 2244,
|
|
71
|
+
slotCosts: [10, 10, 20],
|
|
72
|
+
stepSlots: [2, 2, 3],
|
|
73
|
+
},
|
|
67
74
|
{ name: Cities.Kanazawa, region: Regions.Purple, x: 2207, y: 1385 },
|
|
68
|
-
{ name: Cities.Toyama, region: Regions.Purple, x: 2257, y: 1595 },
|
|
75
|
+
{ name: Cities.Toyama, region: Regions.Purple, x: 2257, y: 1595, slotCosts: [15, 20], stepSlots: [0, 1, 2] },
|
|
69
76
|
{ name: Cities.Kyoto, region: Regions.Purple, x: 1765, y: 1595 },
|
|
70
|
-
{ name: Cities.Osaka, region: Regions.Purple, x: 1517, y: 1631 },
|
|
77
|
+
{ name: Cities.Osaka, region: Regions.Purple, x: 1517, y: 1631, slotCosts: [10, 10, 20], stepSlots: [2, 2, 3] },
|
|
71
78
|
{ name: Cities.Nagoya, region: Regions.Purple, x: 1942, y: 1751 },
|
|
72
79
|
{ name: Cities.Kofu, region: Regions.Purple, x: 2179, y: 1897 },
|
|
73
|
-
{ name: Cities.Hamamatsu, region: Regions.Purple, x: 1958, y: 2024 },
|
|
74
|
-
{ name: Cities.Kobe, region: Regions.Yellow, x: 1555, y: 1451 },
|
|
75
|
-
{ name: Cities.Matsue, region: Regions.Yellow, x: 1457, y: 1006 },
|
|
80
|
+
{ name: Cities.Hamamatsu, region: Regions.Purple, x: 1958, y: 2024, slotCosts: [10, 15] },
|
|
81
|
+
{ name: Cities.Kobe, region: Regions.Yellow, x: 1555, y: 1451, slotCosts: [10, 10, 20], stepSlots: [2, 2, 3] },
|
|
82
|
+
{ name: Cities.Matsue, region: Regions.Yellow, x: 1457, y: 1006, slotCosts: [15, 20], stepSlots: [0, 1, 2] },
|
|
76
83
|
{ name: Cities.Okayama, region: Regions.Yellow, x: 1390, y: 1286 },
|
|
77
84
|
{ name: Cities.Takamatsu, region: Regions.Yellow, x: 1268, y: 1507 },
|
|
78
|
-
{ name: Cities.Kochi, region: Regions.Yellow, x: 974, y: 1472 },
|
|
85
|
+
{ name: Cities.Kochi, region: Regions.Yellow, x: 974, y: 1472, slotCosts: [10, 15] },
|
|
79
86
|
{ name: Cities.Matsuyama, region: Regions.Yellow, x: 1036, y: 1268 },
|
|
80
87
|
{ name: Cities.Hiroshima, region: Regions.Yellow, x: 1103, y: 1078 },
|
|
81
88
|
{ name: Cities.Shimonoseki, region: Regions.Red, x: 856, y: 962 },
|
|
82
|
-
{ name: Cities.Oita, region: Regions.Red, x: 731, y: 1166 },
|
|
83
|
-
{ name: Cities.Miyazaki, region: Regions.Red, x: 469, y: 1318 },
|
|
89
|
+
{ name: Cities.Oita, region: Regions.Red, x: 731, y: 1166, slotCosts: [10, 15] },
|
|
90
|
+
{ name: Cities.Miyazaki, region: Regions.Red, x: 469, y: 1318, slotCosts: [15, 20], stepSlots: [0, 1, 2] },
|
|
84
91
|
{ name: Cities.Kagoshima, region: Regions.Red, x: 157, y: 1255 },
|
|
85
92
|
{ name: Cities.Kumamoto, region: Regions.Red, x: 457, y: 1045 },
|
|
86
|
-
{ name: Cities.Fukuoka, region: Regions.Red, x: 644, y: 823 },
|
|
93
|
+
{ name: Cities.Fukuoka, region: Regions.Red, x: 644, y: 823, slotCosts: [10, 10, 20], stepSlots: [2, 2, 3] },
|
|
87
94
|
{ name: Cities.Nagasaki, region: Regions.Red, x: 313, y: 875 },
|
|
88
95
|
],
|
|
96
|
+
layout: 'Portrait',
|
|
97
|
+
adjustRatio: [0.28, 0.28],
|
|
98
|
+
mapPosition: [0, 120],
|
|
99
|
+
resupply: [
|
|
100
|
+
// Coal
|
|
101
|
+
[
|
|
102
|
+
[3, 4, 3],
|
|
103
|
+
[4, 5, 3],
|
|
104
|
+
[5, 6, 4],
|
|
105
|
+
[5, 7, 5],
|
|
106
|
+
[7, 9, 6], // 6P
|
|
107
|
+
],
|
|
108
|
+
// Oil
|
|
109
|
+
[
|
|
110
|
+
[2, 2, 4],
|
|
111
|
+
[2, 3, 4],
|
|
112
|
+
[3, 4, 5],
|
|
113
|
+
[4, 5, 6],
|
|
114
|
+
[5, 6, 7],
|
|
115
|
+
],
|
|
116
|
+
// Garbage
|
|
117
|
+
[
|
|
118
|
+
[1, 2, 3],
|
|
119
|
+
[1, 2, 3],
|
|
120
|
+
[2, 3, 4],
|
|
121
|
+
[3, 3, 5],
|
|
122
|
+
[3, 5, 6],
|
|
123
|
+
],
|
|
124
|
+
// Uranium
|
|
125
|
+
[
|
|
126
|
+
[1, 1, 1],
|
|
127
|
+
[1, 1, 1],
|
|
128
|
+
[1, 2, 2],
|
|
129
|
+
[2, 3, 2],
|
|
130
|
+
[2, 3, 3],
|
|
131
|
+
],
|
|
132
|
+
],
|
|
133
|
+
startingResources: [21, 15, 9, 3],
|
|
134
|
+
startingCities: ['Fukuoka', 'Kobe', 'Osaka', 'Sapporo', 'Tokyo', 'Yokohama'],
|
|
135
|
+
mapSpecificRules: 'Each player can have two separate networks.\n' +
|
|
136
|
+
'First houses must be placed in one of six starting cities: Fukuoka, Kobe, Osaka, Sapporo, Tokyo, or Yokohama.\n' +
|
|
137
|
+
'Starting cities have two first-connection spots (cost 10 Elektro each); two players can build there in Step 1.\n' +
|
|
138
|
+
'In Step 3, a third connection spot opens in starting cities (cost 20 Elektro).\n' +
|
|
139
|
+
'Some smaller cities have only two building spots (costs 10 and 15, or 15 and 20 from Step 2).\n' +
|
|
140
|
+
'If all spots in every starting city are taken, a player is forced to play with a single network.',
|
|
89
141
|
connections: [
|
|
90
142
|
{ nodes: [Cities.Nagasaki, Cities.Fukuoka], cost: 10 },
|
|
91
143
|
{ nodes: [Cities.Fukuoka, Cities.Shimonoseki], cost: 10 },
|
|
@@ -142,3 +194,4 @@ exports.map = {
|
|
|
142
194
|
{ nodes: [Cities.Kumamoto, Cities.Fukuoka], cost: 8 },
|
|
143
195
|
],
|
|
144
196
|
};
|
|
197
|
+
exports.mapRecharged = { ...exports.map };
|
|
@@ -145,7 +145,7 @@ exports.map = {
|
|
|
145
145
|
[Regions.Orange]: [
|
|
146
146
|
// Denmark
|
|
147
147
|
{ number: 19, type: gamestate_1.PowerPlantType.Oil, cost: 2, citiesPowered: 4 },
|
|
148
|
-
{ number: 26, type: gamestate_1.PowerPlantType.
|
|
148
|
+
{ number: 26, type: gamestate_1.PowerPlantType.Oil, cost: 3, citiesPowered: 5 },
|
|
149
149
|
],
|
|
150
150
|
[Regions.Red]: [
|
|
151
151
|
// Norway
|
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
|
+
slotCosts?: number[];
|
|
10
|
+
stepSlots?: number[];
|
|
9
11
|
singleOccupancy?: boolean;
|
|
10
12
|
island?: string;
|
|
11
13
|
}
|
|
@@ -56,6 +58,7 @@ export interface GameMap {
|
|
|
56
58
|
regionalPowerPlants?: Record<string, PowerPlant[]>;
|
|
57
59
|
crossIslandSurcharge?: number;
|
|
58
60
|
mapSpecificRules?: string;
|
|
61
|
+
startingCities?: string[];
|
|
59
62
|
devBackdrop?: {
|
|
60
63
|
src: string;
|
|
61
64
|
width: number;
|
package/dist/src/maps.js
CHANGED
|
@@ -13,7 +13,7 @@ const france_1 = require("./maps/france");
|
|
|
13
13
|
const germany_1 = require("./maps/germany");
|
|
14
14
|
const indian_1 = require("./maps/indian");
|
|
15
15
|
const italy_1 = require("./maps/italy");
|
|
16
|
-
|
|
16
|
+
const japan_1 = require("./maps/japan");
|
|
17
17
|
const korea_1 = require("./maps/korea");
|
|
18
18
|
const middleeast_1 = require("./maps/middleeast");
|
|
19
19
|
const northamerica_1 = require("./maps/northamerica");
|
|
@@ -45,7 +45,7 @@ exports.maps = [
|
|
|
45
45
|
southafrica_1.map,
|
|
46
46
|
ukireland_1.map,
|
|
47
47
|
// australia,
|
|
48
|
-
|
|
48
|
+
japan_1.map,
|
|
49
49
|
];
|
|
50
50
|
exports.mapsRecharged = [
|
|
51
51
|
america_1.mapRecharged,
|
|
@@ -70,5 +70,5 @@ exports.mapsRecharged = [
|
|
|
70
70
|
ukireland_1.map,
|
|
71
71
|
// australia,
|
|
72
72
|
// china,
|
|
73
|
-
|
|
73
|
+
japan_1.mapRecharged,
|
|
74
74
|
];
|
package/package.json
CHANGED
package/src/available-moves.ts
CHANGED
|
@@ -354,10 +354,41 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
354
354
|
|
|
355
355
|
case Phase.Building: {
|
|
356
356
|
if (player.cities.length < 21) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
357
|
+
const isJapan = G.map.name === 'Japan';
|
|
358
|
+
const japanStartingCities = isJapan ? new Set(G.map.startingCities ?? []) : new Set<string>();
|
|
359
|
+
|
|
360
|
+
let toBuild: { name: string; price: number }[];
|
|
361
|
+
if (player.cities.length == 0) {
|
|
362
|
+
// Japan: first house must go in one of the 6 designated starting cities.
|
|
363
|
+
const candidates = isJapan
|
|
364
|
+
? G.map.cities.filter((c) => japanStartingCities.has(c.name))
|
|
365
|
+
: G.map.cities;
|
|
366
|
+
toBuild = candidates.map((c) => ({ name: c.name, price: 0 }));
|
|
367
|
+
} else if (isJapan && G.round === 1) {
|
|
368
|
+
// Japan round 1: any additional house must also be a starting city.
|
|
369
|
+
// Players cannot extend to adjacent non-starting cities until round 2.
|
|
370
|
+
toBuild = G.map.cities
|
|
371
|
+
.filter((c) => japanStartingCities.has(c.name))
|
|
372
|
+
.map((c) => ({ name: c.name, price: 0 }));
|
|
373
|
+
} else {
|
|
374
|
+
toBuild = dijkstra(G, player).map((c) => ({ name: c.name, price: c.price }));
|
|
375
|
+
|
|
376
|
+
// Japan: a player may start a second disconnected network at any available
|
|
377
|
+
// starting city, paying only the slot cost (no connection fee).
|
|
378
|
+
if (
|
|
379
|
+
isJapan &&
|
|
380
|
+
countNetworks(
|
|
381
|
+
G.map.connections,
|
|
382
|
+
player.cities.map((c) => c.name)
|
|
383
|
+
) < 2
|
|
384
|
+
) {
|
|
385
|
+
toBuild.forEach((city) => {
|
|
386
|
+
if (japanStartingCities.has(city.name)) {
|
|
387
|
+
city.price = 0;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
361
392
|
|
|
362
393
|
toBuild.forEach((city) => {
|
|
363
394
|
const cityData = G.map.cities.find((c) => c.name == city.name)!;
|
|
@@ -428,10 +459,14 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
428
459
|
return;
|
|
429
460
|
}
|
|
430
461
|
|
|
431
|
-
|
|
462
|
+
const slotCosts = cityData.slotCosts;
|
|
463
|
+
const maxSlotsThisStep = cityData.stepSlots ? cityData.stepSlots[G.step - 1] : G.step;
|
|
464
|
+
const totalSlots = slotCosts ? slotCosts.length : 3;
|
|
432
465
|
|
|
433
|
-
if (othersCount
|
|
466
|
+
if (othersCount >= maxSlotsThisStep || othersCount >= totalSlots) {
|
|
434
467
|
city.price = 9999;
|
|
468
|
+
} else {
|
|
469
|
+
city.price += slotCosts ? slotCosts[othersCount] : 10 + othersCount * 5;
|
|
435
470
|
}
|
|
436
471
|
|
|
437
472
|
if (player.cities.find((c) => c.name == city.name)) {
|
|
@@ -569,6 +604,31 @@ export function availableMoves(G: GameState, player: Player): AvailableMoves {
|
|
|
569
604
|
return moves;
|
|
570
605
|
}
|
|
571
606
|
|
|
607
|
+
function countNetworks(connections: { nodes: string[] }[], cityNames: string[]): number {
|
|
608
|
+
const citySet = new Set(cityNames);
|
|
609
|
+
const visited = new Set<string>();
|
|
610
|
+
let count = 0;
|
|
611
|
+
for (const city of cityNames) {
|
|
612
|
+
if (visited.has(city)) continue;
|
|
613
|
+
count++;
|
|
614
|
+
const stack = [city];
|
|
615
|
+
while (stack.length > 0) {
|
|
616
|
+
const current = stack.pop()!;
|
|
617
|
+
if (visited.has(current)) continue;
|
|
618
|
+
visited.add(current);
|
|
619
|
+
for (const conn of connections) {
|
|
620
|
+
if (conn.nodes.includes(current)) {
|
|
621
|
+
const neighbor = conn.nodes.find((n) => n !== current)!;
|
|
622
|
+
if (citySet.has(neighbor) && !visited.has(neighbor)) {
|
|
623
|
+
stack.push(neighbor);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return count;
|
|
630
|
+
}
|
|
631
|
+
|
|
572
632
|
function dijkstra(G: GameState, player: Player): { name: string; price: number }[] {
|
|
573
633
|
const nodes = G.map.cities.map((c) => ({
|
|
574
634
|
name: c.name,
|
package/src/engine.spec.ts
CHANGED
|
@@ -141,6 +141,50 @@ describe('Engine', () => {
|
|
|
141
141
|
expect(ended(G)).to.be.false;
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
+
it('should setup Korea with dual-market populated and players ready to move', () => {
|
|
145
|
+
// Smoke test for the Korea map (PR #74). If a deployed lobby fails to
|
|
146
|
+
// auto-start, setup() is the most likely culprit — this catches that
|
|
147
|
+
// class of bug without needing a recorded game fixture.
|
|
148
|
+
const G = setup(
|
|
149
|
+
2,
|
|
150
|
+
{ map: 'Korea', variant: 'recharged', randomizeMap: false, fastBid: false },
|
|
151
|
+
'korea-test-seed'
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
expect(ended(G)).to.be.false;
|
|
155
|
+
expect(G.map.name).to.equal('Korea');
|
|
156
|
+
|
|
157
|
+
// South-side markets (standard fields, uranium only here)
|
|
158
|
+
expect(G.coalMarket).to.be.greaterThan(0);
|
|
159
|
+
expect(G.oilMarket).to.be.greaterThan(0);
|
|
160
|
+
expect(G.uraniumMarket).to.be.greaterThan(0);
|
|
161
|
+
|
|
162
|
+
// North-side markets (Korea-specific, no uranium row)
|
|
163
|
+
expect(G.coalMarketNorth).to.be.greaterThan(0);
|
|
164
|
+
expect(G.oilMarketNorth).to.be.greaterThan(0);
|
|
165
|
+
expect(G.garbageMarketNorth).to.exist;
|
|
166
|
+
|
|
167
|
+
// Korea uses parallel per-side price tables
|
|
168
|
+
expect(G.coalPricesNorth).to.be.an('array').that.is.not.empty;
|
|
169
|
+
expect(G.oilPricesNorth).to.be.an('array').that.is.not.empty;
|
|
170
|
+
|
|
171
|
+
// At least one player should have available moves (auction phase is live)
|
|
172
|
+
expect(G.players.some((p) => p.availableMoves && Object.keys(p.availableMoves).length > 0)).to.be.true;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should setup Northern Europe and have players ready to move', () => {
|
|
176
|
+
// Smoke test for Northern Europe (PR #74) — parity with Korea test.
|
|
177
|
+
const G = setup(
|
|
178
|
+
2,
|
|
179
|
+
{ map: 'Northern Europe', variant: 'recharged', randomizeMap: false, fastBid: false },
|
|
180
|
+
'ne-test-seed'
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
expect(ended(G)).to.be.false;
|
|
184
|
+
expect(G.map.name).to.equal('Northern Europe');
|
|
185
|
+
expect(G.players.some((p) => p.availableMoves && Object.keys(p.availableMoves).length > 0)).to.be.true;
|
|
186
|
+
});
|
|
187
|
+
|
|
144
188
|
it('should place UK & Ireland Step 3 card third from last with two plants below it', () => {
|
|
145
189
|
// UK & Ireland rules: the Step 3 card (plant 99) goes at deck.length - 3
|
|
146
190
|
// so two plants sit below it, and Step 3 fires two auctions earlier than
|
package/src/engine.ts
CHANGED
|
@@ -15,7 +15,7 @@ export const playerColors = ['limegreen', 'mediumorchid', 'red', 'dodgerblue', '
|
|
|
15
15
|
|
|
16
16
|
const citiesToStep2 = [10, 7, 7, 7, 6];
|
|
17
17
|
const citiesToStep2BadenWurttemberg = [9, 6, 6, 6, 5];
|
|
18
|
-
const citiesToStep2UKIreland = [
|
|
18
|
+
const citiesToStep2UKIreland = [10, 7, 7, 7, 6];
|
|
19
19
|
const citiesToEndGame = [21, 17, 17, 15, 14];
|
|
20
20
|
const cityIncome = [10, 22, 33, 44, 54, 64, 73, 82, 90, 98, 105, 112, 118, 124, 129, 134, 138, 142, 145, 148, 150, 150];
|
|
21
21
|
const regionsInPlay = [3, 3, 4, 5, 5];
|
|
@@ -621,35 +621,34 @@ export function move(G: GameState, move: Move, playerNumber: number, isUndo = fa
|
|
|
621
621
|
if (G.players.some((p) => !p.skipAuction && !p.isDropped)) {
|
|
622
622
|
nextPlayerAuction(G);
|
|
623
623
|
} else {
|
|
624
|
-
if (
|
|
624
|
+
if (G.auctionSkips == G.players.length && G.map.name == 'Baden-Württemberg') {
|
|
625
|
+
// Baden-Württemberg: remove the two lowest plants when no one buys.
|
|
626
|
+
// This applies regardless of variant.
|
|
627
|
+
const removed: number[] = [];
|
|
628
|
+
for (let i = 0; i < 2 && G.actualMarket.length > 0; i++) {
|
|
629
|
+
removed.push(G.actualMarket[0].number);
|
|
630
|
+
G.actualMarket.shift();
|
|
631
|
+
addPowerPlant(G);
|
|
632
|
+
}
|
|
633
|
+
G.log.push({
|
|
634
|
+
type: 'event',
|
|
635
|
+
event: `Everyone passed, removing the two lowest numbered Power Plants (${removed.join(
|
|
636
|
+
', '
|
|
637
|
+
)}).`,
|
|
638
|
+
});
|
|
639
|
+
} else if (
|
|
625
640
|
G.auctionSkips == G.players.length &&
|
|
626
641
|
G.options.variant == 'original' &&
|
|
627
642
|
G.map.name != 'China' &&
|
|
628
643
|
G.map.name != 'Russia'
|
|
629
644
|
) {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
removed.push(G.actualMarket[0].number);
|
|
635
|
-
G.actualMarket.shift();
|
|
636
|
-
addPowerPlant(G);
|
|
637
|
-
}
|
|
638
|
-
G.log.push({
|
|
639
|
-
type: 'event',
|
|
640
|
-
event: `Everyone passed, removing the two lowest numbered Power Plants (${removed.join(
|
|
641
|
-
', '
|
|
642
|
-
)}).`,
|
|
643
|
-
});
|
|
644
|
-
} else {
|
|
645
|
-
G.log.push({
|
|
646
|
-
type: 'event',
|
|
647
|
-
event: `Everyone passed, removing lowest numbered Power Plant (${G.actualMarket[0].number}).`,
|
|
648
|
-
});
|
|
645
|
+
G.log.push({
|
|
646
|
+
type: 'event',
|
|
647
|
+
event: `Everyone passed, removing lowest numbered Power Plant (${G.actualMarket[0].number}).`,
|
|
648
|
+
});
|
|
649
649
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
}
|
|
650
|
+
G.actualMarket.shift();
|
|
651
|
+
addPowerPlant(G);
|
|
653
652
|
}
|
|
654
653
|
|
|
655
654
|
toResourcesPhase(G);
|
package/src/gamestate.ts
CHANGED
|
@@ -23,9 +23,9 @@ export type MapName =
|
|
|
23
23
|
| 'Europe'
|
|
24
24
|
| 'North America'
|
|
25
25
|
| 'South Africa'
|
|
26
|
-
| 'UK & Ireland'
|
|
26
|
+
| 'UK & Ireland'
|
|
27
|
+
| 'Japan';
|
|
27
28
|
// | 'Australia'
|
|
28
|
-
// | 'Japan'
|
|
29
29
|
export type Variant = 'original' | 'recharged';
|
|
30
30
|
|
|
31
31
|
export interface GameOptions {
|
|
@@ -55,8 +55,24 @@ export const map: GameMap = {
|
|
|
55
55
|
{ name: Cities.Sigmarincen, region: Regions.Purple, x: 1111, y: 1803 },
|
|
56
56
|
{ name: Cities.Konstanz, region: Regions.Purple, x: 1088, y: 2163 },
|
|
57
57
|
{ name: Cities.Ulm, region: Regions.Purple, x: 1541, y: 1499 },
|
|
58
|
-
{
|
|
59
|
-
|
|
58
|
+
{
|
|
59
|
+
name: Cities.Augsburg,
|
|
60
|
+
region: Regions.Purple,
|
|
61
|
+
x: 1830,
|
|
62
|
+
y: 1505,
|
|
63
|
+
transregional: true,
|
|
64
|
+
slotCosts: [15, 20],
|
|
65
|
+
stepSlots: [0, 1, 2],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: Cities.Basel,
|
|
69
|
+
region: Regions.Green,
|
|
70
|
+
x: 103,
|
|
71
|
+
y: 2271,
|
|
72
|
+
transregional: true,
|
|
73
|
+
slotCosts: [15, 20],
|
|
74
|
+
stepSlots: [0, 1, 2],
|
|
75
|
+
},
|
|
60
76
|
{ name: Cities.Waldshuttiencen, region: Regions.Green, x: 502, y: 2166 },
|
|
61
77
|
{ name: Cities.Singen, region: Regions.Green, x: 874, y: 2084 },
|
|
62
78
|
{ name: Cities.Tuttlincen, region: Regions.Green, x: 870, y: 1854 },
|
|
@@ -69,8 +85,24 @@ export const map: GameMap = {
|
|
|
69
85
|
{ name: Cities.Sinsheim, region: Regions.Red, x: 920, y: 785 },
|
|
70
86
|
{ name: Cities.Heidelber, region: Regions.Red, x: 817, y: 660 },
|
|
71
87
|
{ name: Cities.Mannheim, region: Regions.Red, x: 710, y: 540 },
|
|
72
|
-
{
|
|
73
|
-
|
|
88
|
+
{
|
|
89
|
+
name: Cities.Luowigshafen,
|
|
90
|
+
region: Regions.Red,
|
|
91
|
+
x: 579,
|
|
92
|
+
y: 588,
|
|
93
|
+
transregional: true,
|
|
94
|
+
slotCosts: [15, 20],
|
|
95
|
+
stepSlots: [0, 1, 2],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: Cities.Nurnberg,
|
|
99
|
+
region: Regions.Blue,
|
|
100
|
+
x: 1837,
|
|
101
|
+
y: 771,
|
|
102
|
+
transregional: true,
|
|
103
|
+
slotCosts: [15, 20],
|
|
104
|
+
stepSlots: [0, 1, 2],
|
|
105
|
+
},
|
|
74
106
|
{ name: Cities.Ellwangen, region: Regions.Blue, x: 1637, y: 1030 },
|
|
75
107
|
{ name: Cities.Coppingen, region: Regions.Blue, x: 1387, y: 1233 },
|
|
76
108
|
{ name: Cities.Reutlincen, region: Regions.Blue, x: 1107, y: 1443 },
|
|
@@ -80,7 +112,15 @@ export const map: GameMap = {
|
|
|
80
112
|
{ name: Cities.Laha, region: Regions.Yellow, x: 296, y: 1575 },
|
|
81
113
|
{ name: Cities.Badenbaden, region: Regions.Yellow, x: 554, y: 1259 },
|
|
82
114
|
{ name: Cities.Offenburg, region: Regions.Yellow, x: 395, y: 1422 },
|
|
83
|
-
{
|
|
115
|
+
{
|
|
116
|
+
name: Cities.Strasbourg,
|
|
117
|
+
region: Regions.Yellow,
|
|
118
|
+
x: 211,
|
|
119
|
+
y: 1330,
|
|
120
|
+
transregional: true,
|
|
121
|
+
slotCosts: [15, 20],
|
|
122
|
+
stepSlots: [0, 1, 2],
|
|
123
|
+
},
|
|
84
124
|
{ name: Cities.Pforzheim, region: Regions.Yellow, x: 794, y: 1095 },
|
|
85
125
|
{ name: Cities.Rastatt, region: Regions.Yellow, x: 509, y: 1109 },
|
|
86
126
|
{ name: Cities.Karlsruhf, region: Regions.Yellow, x: 638, y: 974 },
|
package/src/maps/japan.ts
CHANGED
|
@@ -49,42 +49,95 @@ export enum Cities {
|
|
|
49
49
|
export const map: GameMap = {
|
|
50
50
|
name: 'Japan',
|
|
51
51
|
cities: [
|
|
52
|
-
{ name: Cities.Asahikawa, region: Regions.Brown, x: 3384, y:
|
|
53
|
-
{ name: Cities.Sapporo, region: Regions.Brown, x: 3028, y:
|
|
54
|
-
{ name: Cities.Hakodate, region: Regions.Brown, x: 2622, y:
|
|
52
|
+
{ name: Cities.Asahikawa, region: Regions.Brown, x: 3384, y: 1014 },
|
|
53
|
+
{ name: Cities.Sapporo, region: Regions.Brown, x: 3028, y: 982, slotCosts: [10, 10, 20], stepSlots: [2, 2, 3] },
|
|
54
|
+
{ name: Cities.Hakodate, region: Regions.Brown, x: 2622, y: 1112 },
|
|
55
55
|
{ name: Cities.Admori, region: Regions.Brown, x: 3818, y: 1336 },
|
|
56
|
-
{ name: Cities.Morioka, region: Regions.Brown, x: 3636, y: 1646 },
|
|
56
|
+
{ name: Cities.Morioka, region: Regions.Brown, x: 3636, y: 1646, slotCosts: [15, 20], stepSlots: [0, 1, 2] },
|
|
57
57
|
{ name: Cities.Akita, region: Regions.Brown, x: 3472, y: 1480 },
|
|
58
58
|
{ name: Cities.Sendai, region: Regions.Brown, x: 3408, y: 1886 },
|
|
59
59
|
{ name: Cities.Niigata, region: Regions.Green, x: 2874, y: 1628 },
|
|
60
|
-
{ name: Cities.Koriyama, region: Regions.Green, x: 3086, y: 1932 },
|
|
60
|
+
{ name: Cities.Koriyama, region: Regions.Green, x: 3086, y: 1932, slotCosts: [10, 15] },
|
|
61
61
|
{ name: Cities.Nagano, region: Regions.Green, x: 2486, y: 1722 },
|
|
62
|
-
{ name: Cities.Saitama, region: Regions.Green, x: 2450, y: 1986 },
|
|
63
|
-
{ name: Cities.Tokyo, region: Regions.Green, x: 2524, y: 2122 },
|
|
62
|
+
{ name: Cities.Saitama, region: Regions.Green, x: 2450, y: 1986, slotCosts: [15, 20], stepSlots: [0, 1, 2] },
|
|
63
|
+
{ name: Cities.Tokyo, region: Regions.Green, x: 2524, y: 2122, slotCosts: [10, 10, 20], stepSlots: [2, 2, 3] },
|
|
64
64
|
{ name: Cities.Chiba, region: Regions.Green, x: 2650, y: 2316 },
|
|
65
|
-
{
|
|
65
|
+
{
|
|
66
|
+
name: Cities.Yokohama,
|
|
67
|
+
region: Regions.Green,
|
|
68
|
+
x: 2306,
|
|
69
|
+
y: 2244,
|
|
70
|
+
slotCosts: [10, 10, 20],
|
|
71
|
+
stepSlots: [2, 2, 3],
|
|
72
|
+
},
|
|
66
73
|
{ name: Cities.Kanazawa, region: Regions.Purple, x: 2207, y: 1385 },
|
|
67
|
-
{ name: Cities.Toyama, region: Regions.Purple, x: 2257, y: 1595 },
|
|
74
|
+
{ name: Cities.Toyama, region: Regions.Purple, x: 2257, y: 1595, slotCosts: [15, 20], stepSlots: [0, 1, 2] },
|
|
68
75
|
{ name: Cities.Kyoto, region: Regions.Purple, x: 1765, y: 1595 },
|
|
69
|
-
{ name: Cities.Osaka, region: Regions.Purple, x: 1517, y: 1631 },
|
|
76
|
+
{ name: Cities.Osaka, region: Regions.Purple, x: 1517, y: 1631, slotCosts: [10, 10, 20], stepSlots: [2, 2, 3] },
|
|
70
77
|
{ name: Cities.Nagoya, region: Regions.Purple, x: 1942, y: 1751 },
|
|
71
78
|
{ name: Cities.Kofu, region: Regions.Purple, x: 2179, y: 1897 },
|
|
72
|
-
{ name: Cities.Hamamatsu, region: Regions.Purple, x: 1958, y: 2024 },
|
|
73
|
-
{ name: Cities.Kobe, region: Regions.Yellow, x: 1555, y: 1451 },
|
|
74
|
-
{ name: Cities.Matsue, region: Regions.Yellow, x: 1457, y: 1006 },
|
|
79
|
+
{ name: Cities.Hamamatsu, region: Regions.Purple, x: 1958, y: 2024, slotCosts: [10, 15] },
|
|
80
|
+
{ name: Cities.Kobe, region: Regions.Yellow, x: 1555, y: 1451, slotCosts: [10, 10, 20], stepSlots: [2, 2, 3] },
|
|
81
|
+
{ name: Cities.Matsue, region: Regions.Yellow, x: 1457, y: 1006, slotCosts: [15, 20], stepSlots: [0, 1, 2] },
|
|
75
82
|
{ name: Cities.Okayama, region: Regions.Yellow, x: 1390, y: 1286 },
|
|
76
83
|
{ name: Cities.Takamatsu, region: Regions.Yellow, x: 1268, y: 1507 },
|
|
77
|
-
{ name: Cities.Kochi, region: Regions.Yellow, x: 974, y: 1472 },
|
|
84
|
+
{ name: Cities.Kochi, region: Regions.Yellow, x: 974, y: 1472, slotCosts: [10, 15] },
|
|
78
85
|
{ name: Cities.Matsuyama, region: Regions.Yellow, x: 1036, y: 1268 },
|
|
79
86
|
{ name: Cities.Hiroshima, region: Regions.Yellow, x: 1103, y: 1078 },
|
|
80
87
|
{ name: Cities.Shimonoseki, region: Regions.Red, x: 856, y: 962 },
|
|
81
|
-
{ name: Cities.Oita, region: Regions.Red, x: 731, y: 1166 },
|
|
82
|
-
{ name: Cities.Miyazaki, region: Regions.Red, x: 469, y: 1318 },
|
|
88
|
+
{ name: Cities.Oita, region: Regions.Red, x: 731, y: 1166, slotCosts: [10, 15] },
|
|
89
|
+
{ name: Cities.Miyazaki, region: Regions.Red, x: 469, y: 1318, slotCosts: [15, 20], stepSlots: [0, 1, 2] },
|
|
83
90
|
{ name: Cities.Kagoshima, region: Regions.Red, x: 157, y: 1255 },
|
|
84
91
|
{ name: Cities.Kumamoto, region: Regions.Red, x: 457, y: 1045 },
|
|
85
|
-
{ name: Cities.Fukuoka, region: Regions.Red, x: 644, y: 823 },
|
|
92
|
+
{ name: Cities.Fukuoka, region: Regions.Red, x: 644, y: 823, slotCosts: [10, 10, 20], stepSlots: [2, 2, 3] },
|
|
86
93
|
{ name: Cities.Nagasaki, region: Regions.Red, x: 313, y: 875 },
|
|
87
94
|
],
|
|
95
|
+
layout: 'Portrait',
|
|
96
|
+
adjustRatio: [0.28, 0.28],
|
|
97
|
+
mapPosition: [0, 120],
|
|
98
|
+
resupply: [
|
|
99
|
+
// Coal
|
|
100
|
+
[
|
|
101
|
+
[3, 4, 3], // 2P
|
|
102
|
+
[4, 5, 3], // 3P
|
|
103
|
+
[5, 6, 4], // 4P
|
|
104
|
+
[5, 7, 5], // 5P
|
|
105
|
+
[7, 9, 6], // 6P
|
|
106
|
+
],
|
|
107
|
+
// Oil
|
|
108
|
+
[
|
|
109
|
+
[2, 2, 4],
|
|
110
|
+
[2, 3, 4],
|
|
111
|
+
[3, 4, 5],
|
|
112
|
+
[4, 5, 6],
|
|
113
|
+
[5, 6, 7],
|
|
114
|
+
],
|
|
115
|
+
// Garbage
|
|
116
|
+
[
|
|
117
|
+
[1, 2, 3],
|
|
118
|
+
[1, 2, 3],
|
|
119
|
+
[2, 3, 4],
|
|
120
|
+
[3, 3, 5],
|
|
121
|
+
[3, 5, 6],
|
|
122
|
+
],
|
|
123
|
+
// Uranium
|
|
124
|
+
[
|
|
125
|
+
[1, 1, 1],
|
|
126
|
+
[1, 1, 1],
|
|
127
|
+
[1, 2, 2],
|
|
128
|
+
[2, 3, 2],
|
|
129
|
+
[2, 3, 3],
|
|
130
|
+
],
|
|
131
|
+
],
|
|
132
|
+
startingResources: [21, 15, 9, 3],
|
|
133
|
+
startingCities: ['Fukuoka', 'Kobe', 'Osaka', 'Sapporo', 'Tokyo', 'Yokohama'],
|
|
134
|
+
mapSpecificRules:
|
|
135
|
+
'Each player can have two separate networks.\n' +
|
|
136
|
+
'First houses must be placed in one of six starting cities: Fukuoka, Kobe, Osaka, Sapporo, Tokyo, or Yokohama.\n' +
|
|
137
|
+
'Starting cities have two first-connection spots (cost 10 Elektro each); two players can build there in Step 1.\n' +
|
|
138
|
+
'In Step 3, a third connection spot opens in starting cities (cost 20 Elektro).\n' +
|
|
139
|
+
'Some smaller cities have only two building spots (costs 10 and 15, or 15 and 20 from Step 2).\n' +
|
|
140
|
+
'If all spots in every starting city are taken, a player is forced to play with a single network.',
|
|
88
141
|
connections: [
|
|
89
142
|
{ nodes: [Cities.Nagasaki, Cities.Fukuoka], cost: 10 },
|
|
90
143
|
{ nodes: [Cities.Fukuoka, Cities.Shimonoseki], cost: 10 },
|
|
@@ -141,3 +194,5 @@ export const map: GameMap = {
|
|
|
141
194
|
{ nodes: [Cities.Kumamoto, Cities.Fukuoka], cost: 8 },
|
|
142
195
|
],
|
|
143
196
|
};
|
|
197
|
+
|
|
198
|
+
export const mapRecharged: GameMap = { ...map };
|
|
@@ -144,7 +144,7 @@ export const map: GameMap = {
|
|
|
144
144
|
[Regions.Orange]: [
|
|
145
145
|
// Denmark
|
|
146
146
|
{ number: 19, type: PowerPlantType.Oil, cost: 2, citiesPowered: 4 },
|
|
147
|
-
{ number: 26, type: PowerPlantType.
|
|
147
|
+
{ number: 26, type: PowerPlantType.Oil, cost: 3, citiesPowered: 5 },
|
|
148
148
|
],
|
|
149
149
|
[Regions.Red]: [
|
|
150
150
|
// Norway
|
package/src/maps.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { map as france } from './maps/france';
|
|
|
12
12
|
import { map as germany, mapRecharged as germanyRecharged } from './maps/germany';
|
|
13
13
|
import { map as indian } from './maps/indian';
|
|
14
14
|
import { map as italy } from './maps/italy';
|
|
15
|
-
|
|
15
|
+
import { map as japan, mapRecharged as japanRecharged } from './maps/japan';
|
|
16
16
|
import { map as korea } from './maps/korea';
|
|
17
17
|
import { map as middleeast } from './maps/middleeast';
|
|
18
18
|
import { map as northamerica } from './maps/northamerica';
|
|
@@ -32,6 +32,12 @@ export interface City {
|
|
|
32
32
|
// and connecting to them costs a fixed price (transregionalConnectionCost on the GameMap)
|
|
33
33
|
// instead of the normal dijkstra path cost.
|
|
34
34
|
transregional?: boolean;
|
|
35
|
+
// Custom per-slot building costs (e.g. Japan). Length also determines total slots.
|
|
36
|
+
// Defaults to [10, 15, 20] if omitted.
|
|
37
|
+
slotCosts?: number[];
|
|
38
|
+
// Max slots open per step [step1, step2, step3].
|
|
39
|
+
// Defaults to [1, 2, 3] (standard Power Grid rules) if omitted.
|
|
40
|
+
stepSlots?: number[];
|
|
35
41
|
// South Africa's six cross-border foreign-country spaces: only one player ever
|
|
36
42
|
// builds here (cap 1 instead of the standard 3), and the build skips the
|
|
37
43
|
// 10+position*5 house-base cost — the dijkstra path cost (the 30-Elektro edge)
|
|
@@ -106,6 +112,10 @@ export interface GameMap {
|
|
|
106
112
|
// cost (there is no sea edge) and pay 10+position*5 + this surcharge.
|
|
107
113
|
crossIslandSurcharge?: number;
|
|
108
114
|
mapSpecificRules?: string;
|
|
115
|
+
// Cities where a player's first house (or second network) must be placed.
|
|
116
|
+
// Used by Japan: only the 6 designated starting cities are valid first builds,
|
|
117
|
+
// and a second disconnected network can only begin in one of these cities.
|
|
118
|
+
startingCities?: string[];
|
|
109
119
|
// Dev-only: when set, the viewer renders an `<image>` backdrop behind the
|
|
110
120
|
// map and logs click positions (in local SVG coords) to the console as
|
|
111
121
|
// ready-to-paste `{ name, region, x, y }` lines. Intended for authoring
|
|
@@ -137,7 +147,7 @@ export const maps: GameMap[] = [
|
|
|
137
147
|
southafrica,
|
|
138
148
|
ukireland,
|
|
139
149
|
// australia,
|
|
140
|
-
|
|
150
|
+
japan,
|
|
141
151
|
];
|
|
142
152
|
|
|
143
153
|
export const mapsRecharged: GameMap[] = [
|
|
@@ -163,5 +173,5 @@ export const mapsRecharged: GameMap[] = [
|
|
|
163
173
|
ukireland,
|
|
164
174
|
// australia,
|
|
165
175
|
// china,
|
|
166
|
-
|
|
176
|
+
japanRecharged,
|
|
167
177
|
];
|