powergrid-viewer 1.7.0 → 1.9.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "powergrid-viewer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/boardgamers/powergrid.git",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"vue": "^2.6.11",
|
|
19
19
|
"vue-class-component": "^7.2.3",
|
|
20
20
|
"vue-property-decorator": "^8.4.2",
|
|
21
|
-
"powergrid-engine": "1.
|
|
21
|
+
"powergrid-engine": "1.13.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/assert": "^1.4.7",
|
package/src/components/Game.vue
CHANGED
|
@@ -49,12 +49,13 @@
|
|
|
49
49
|
|
|
50
50
|
<Map
|
|
51
51
|
ref="map"
|
|
52
|
-
:transform="
|
|
52
|
+
:transform="mapTransform"
|
|
53
53
|
:playerColors="playerColors"
|
|
54
54
|
:cities="G.map.cities"
|
|
55
55
|
:connections="G.map.connections"
|
|
56
56
|
:polygons="G.map.polygons"
|
|
57
57
|
:buildableCities="getBuildableCities()"
|
|
58
|
+
:devBackdrop="G.map.devBackdrop"
|
|
58
59
|
@build="build($event)"
|
|
59
60
|
/>
|
|
60
61
|
|
|
@@ -68,6 +69,7 @@
|
|
|
68
69
|
G.map.name == 'Middle East' ? Math.max(G.oilMarket - G.oilPrices.filter((p) => p > 1).length, 0) : 0
|
|
69
70
|
"
|
|
70
71
|
:buyableResources="buyableResources()"
|
|
72
|
+
:coalStorage="G.coalStorage"
|
|
71
73
|
:resourceResupply="getResourceResupply()"
|
|
72
74
|
@buyResource="buyResource($event)"
|
|
73
75
|
/>
|
|
@@ -630,11 +632,14 @@ export default class Game extends Vue {
|
|
|
630
632
|
}
|
|
631
633
|
}
|
|
632
634
|
|
|
633
|
-
buyResource(payload: { resource: ResourceType, side?: 'north' | 'south' }) {
|
|
634
|
-
const data: { resource: ResourceType, side?: 'north' | 'south' } = { resource: payload.resource };
|
|
635
|
+
buyResource(payload: { resource: ResourceType, side?: 'north' | 'south', fromStorage?: boolean }) {
|
|
636
|
+
const data: { resource: ResourceType, side?: 'north' | 'south', fromStorage?: boolean } = { resource: payload.resource };
|
|
635
637
|
if (payload.side) {
|
|
636
638
|
data.side = payload.side;
|
|
637
639
|
}
|
|
640
|
+
if (payload.fromStorage) {
|
|
641
|
+
data.fromStorage = payload.fromStorage;
|
|
642
|
+
}
|
|
638
643
|
this.sendMove({ name: MoveName.BuyResource, data });
|
|
639
644
|
}
|
|
640
645
|
|
|
@@ -875,7 +880,7 @@ export default class Game extends Vue {
|
|
|
875
880
|
return !!availableMoves[MoveName.ChoosePowerPlant];
|
|
876
881
|
}
|
|
877
882
|
|
|
878
|
-
buyableResources(): { resource: ResourceType, side?: 'north' | 'south' }[] {
|
|
883
|
+
buyableResources(): { resource: ResourceType, side?: 'north' | 'south', fromStorage?: boolean }[] {
|
|
879
884
|
if (!this.canMove()) return [];
|
|
880
885
|
|
|
881
886
|
const currentPlayer = this.G!.players[this.player!];
|
|
@@ -1097,6 +1102,19 @@ export default class Game extends Vue {
|
|
|
1097
1102
|
return playersSortedByScore(this.G!);
|
|
1098
1103
|
}
|
|
1099
1104
|
|
|
1105
|
+
get mapTransform() {
|
|
1106
|
+
if (!this.G?.map) return '';
|
|
1107
|
+
const [mx, my] = this.G.map.mapPosition!;
|
|
1108
|
+
const rotation = this.G.map.mapRotation;
|
|
1109
|
+
if (!rotation) return `translate(${mx}, ${my})`;
|
|
1110
|
+
const cities = this.G.map.cities;
|
|
1111
|
+
const xs = cities.map((c) => c.x);
|
|
1112
|
+
const ys = cities.map((c) => c.y);
|
|
1113
|
+
const cx = (Math.min(...xs) + Math.max(...xs)) / 2;
|
|
1114
|
+
const cy = (Math.min(...ys) + Math.max(...ys)) / 2;
|
|
1115
|
+
return `translate(${mx}, ${my}) rotate(${rotation}, ${cx}, ${cy})`;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1100
1118
|
get adjustedPlayerOrder() {
|
|
1101
1119
|
if (!this.G) {
|
|
1102
1120
|
return [];
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<g>
|
|
2
|
+
<g @click="onPickCoord">
|
|
3
|
+
<!-- Dev-only backdrop for coord-picking. See GameMap.devBackdrop. -->
|
|
4
|
+
<image
|
|
5
|
+
v-if="devBackdrop"
|
|
6
|
+
:href="devBackdrop.src"
|
|
7
|
+
x="0"
|
|
8
|
+
y="0"
|
|
9
|
+
:width="devBackdrop.width"
|
|
10
|
+
:height="devBackdrop.height"
|
|
11
|
+
:opacity="devBackdrop.opacity != null ? devBackdrop.opacity : 0.5"
|
|
12
|
+
/>
|
|
13
|
+
|
|
3
14
|
<!-- <template v-for="polygon in polygons">
|
|
4
15
|
<polygon
|
|
5
16
|
:key="'pol_ ' + polygon.region"
|
|
@@ -17,6 +28,27 @@
|
|
|
17
28
|
<circle :key="city.name + '_circle'" r="20" :cx="city.x" :cy="city.y" fill="gray" stroke="black">
|
|
18
29
|
<title>{{ city.name }}</title>
|
|
19
30
|
</circle>
|
|
31
|
+
<!-- South Africa cross-border spaces: render a small red pennant
|
|
32
|
+
above the city. Marks a single-occupancy space ($30 total cost). -->
|
|
33
|
+
<g v-if="city.singleOccupancy" :key="city.name + '_flag'">
|
|
34
|
+
<line
|
|
35
|
+
:x1="city.x + 12"
|
|
36
|
+
:y1="city.y - 12"
|
|
37
|
+
:x2="city.x + 12"
|
|
38
|
+
:y2="city.y - 32"
|
|
39
|
+
stroke="black"
|
|
40
|
+
stroke-width="1.5"
|
|
41
|
+
/>
|
|
42
|
+
<polygon
|
|
43
|
+
:points="`${city.x + 12},${city.y - 32} ${city.x + 26},${city.y - 28} ${city.x + 12},${
|
|
44
|
+
city.y - 24
|
|
45
|
+
}`"
|
|
46
|
+
fill="red"
|
|
47
|
+
stroke="black"
|
|
48
|
+
stroke-width="1"
|
|
49
|
+
/>
|
|
50
|
+
<title>{{ city.name }} — cross-border space (1 house max, 30 Elektro total cost)</title>
|
|
51
|
+
</g>
|
|
20
52
|
</template>
|
|
21
53
|
|
|
22
54
|
<template v-for="connection in connections">
|
|
@@ -96,6 +128,55 @@
|
|
|
96
128
|
/>
|
|
97
129
|
</template>
|
|
98
130
|
|
|
131
|
+
<template v-for="city in cities">
|
|
132
|
+
<!-- [10,15] city: X at bottom-right -->
|
|
133
|
+
<template v-if="city.slotCosts && city.slotCosts.length === 2 && city.slotCosts[0] === 10">
|
|
134
|
+
<line
|
|
135
|
+
:key="city.name + '_x1'"
|
|
136
|
+
:x1="city.x + 10"
|
|
137
|
+
:y1="city.y + 10"
|
|
138
|
+
:x2="city.x + 19"
|
|
139
|
+
:y2="city.y + 19"
|
|
140
|
+
stroke="black"
|
|
141
|
+
stroke-width="3"
|
|
142
|
+
stroke-linecap="round"
|
|
143
|
+
/>
|
|
144
|
+
<line
|
|
145
|
+
:key="city.name + '_x2'"
|
|
146
|
+
:x1="city.x + 19"
|
|
147
|
+
:y1="city.y + 10"
|
|
148
|
+
:x2="city.x + 10"
|
|
149
|
+
:y2="city.y + 19"
|
|
150
|
+
stroke="black"
|
|
151
|
+
stroke-width="3"
|
|
152
|
+
stroke-linecap="round"
|
|
153
|
+
/>
|
|
154
|
+
</template>
|
|
155
|
+
<!-- [15,20] city: X at top-middle -->
|
|
156
|
+
<template v-if="city.slotCosts && city.slotCosts.length === 2 && city.slotCosts[0] === 15">
|
|
157
|
+
<line
|
|
158
|
+
:key="city.name + '_x1'"
|
|
159
|
+
:x1="city.x - 5"
|
|
160
|
+
:y1="city.y - 21"
|
|
161
|
+
:x2="city.x + 5"
|
|
162
|
+
:y2="city.y - 11"
|
|
163
|
+
stroke="black"
|
|
164
|
+
stroke-width="3"
|
|
165
|
+
stroke-linecap="round"
|
|
166
|
+
/>
|
|
167
|
+
<line
|
|
168
|
+
:key="city.name + '_x2'"
|
|
169
|
+
:x1="city.x + 5"
|
|
170
|
+
:y1="city.y - 21"
|
|
171
|
+
:x2="city.x - 5"
|
|
172
|
+
:y2="city.y - 11"
|
|
173
|
+
stroke="black"
|
|
174
|
+
stroke-width="3"
|
|
175
|
+
stroke-linecap="round"
|
|
176
|
+
/>
|
|
177
|
+
</template>
|
|
178
|
+
</template>
|
|
179
|
+
|
|
99
180
|
<template v-if="!preferences.disableHelp">
|
|
100
181
|
<template v-for="city in cities">
|
|
101
182
|
<circle
|
|
@@ -132,6 +213,7 @@ export default class Map extends Vue {
|
|
|
132
213
|
@Prop() connections?: Connection[];
|
|
133
214
|
@Prop() playerColors?: string[];
|
|
134
215
|
@Prop() buildableCities?: string[];
|
|
216
|
+
@Prop() devBackdrop?: { src: string; width: number; height: number; opacity?: number };
|
|
135
217
|
|
|
136
218
|
@Inject() preferences!: Preferences;
|
|
137
219
|
|
|
@@ -192,5 +274,15 @@ export default class Map extends Vue {
|
|
|
192
274
|
build(city: City) {
|
|
193
275
|
this.$emit('build', city);
|
|
194
276
|
}
|
|
277
|
+
|
|
278
|
+
onPickCoord(e: MouseEvent) {
|
|
279
|
+
if (!this.devBackdrop) return;
|
|
280
|
+
const g = e.currentTarget as SVGGElement;
|
|
281
|
+
const ctm = g.getScreenCTM();
|
|
282
|
+
if (!ctm) return;
|
|
283
|
+
const pt = new DOMPoint(e.clientX, e.clientY).matrixTransform(ctm.inverse());
|
|
284
|
+
// eslint-disable-next-line no-console
|
|
285
|
+
console.log(`{ name: Cities.TODO, region: Regions.TODO, x: ${Math.round(pt.x)}, y: ${Math.round(pt.y)} },`);
|
|
286
|
+
}
|
|
195
287
|
}
|
|
196
288
|
</script>
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
:key="coal.id"
|
|
40
40
|
:pieceId="coal.id"
|
|
41
41
|
:targetState="{ x: coal.x, y: coal.y }"
|
|
42
|
-
:canClick="!coal.transparent && canBuyResource('coal', coal.side)"
|
|
42
|
+
:canClick="!coal.transparent && canBuyResource('coal', coal.side, coal.fromStorage)"
|
|
43
43
|
:transparent="coal.transparent"
|
|
44
44
|
:scale="0.08"
|
|
45
|
-
@click="buyResource('coal', coal.side)"
|
|
45
|
+
@click="buyResource('coal', coal.side, coal.fromStorage)"
|
|
46
46
|
/>
|
|
47
47
|
</template>
|
|
48
48
|
<template v-for="oil in oilsNorth">
|
|
@@ -92,9 +92,18 @@
|
|
|
92
92
|
</text>
|
|
93
93
|
<Uranium :pieceId="-1" :targetState="{ x: 428, y: 12 }" :canClick="false" :transparent="false" />
|
|
94
94
|
|
|
95
|
-
<rect
|
|
95
|
+
<rect
|
|
96
|
+
v-if="!isIndiaResourceMarket && !isNinePriceMarket"
|
|
97
|
+
width="760"
|
|
98
|
+
height="80"
|
|
99
|
+
x="20"
|
|
100
|
+
y="40"
|
|
101
|
+
rx="3"
|
|
102
|
+
fill="goldenrod"
|
|
103
|
+
/>
|
|
104
|
+
<rect v-if="isNinePriceMarket" width="780" height="80" x="20" y="40" rx="3" fill="goldenrod" />
|
|
96
105
|
<rect v-if="isIndiaResourceMarket" width="680" height="80" x="20" y="40" rx="3" fill="goldenrod" />
|
|
97
|
-
<template v-for="index in 8">
|
|
106
|
+
<template v-for="index in isNinePriceMarket ? 9 : 8">
|
|
98
107
|
<rect
|
|
99
108
|
:key="'resources' + index"
|
|
100
109
|
width="70"
|
|
@@ -115,7 +124,7 @@
|
|
|
115
124
|
>
|
|
116
125
|
{{ index }}
|
|
117
126
|
</text>
|
|
118
|
-
<g :key="'lines' + index" v-if="!isIndiaResourceMarket">
|
|
127
|
+
<g :key="'lines' + index" v-if="!isIndiaResourceMarket && !isNinePriceMarket">
|
|
119
128
|
<line :x1="25 + 85 * (index - 1)" y1="68" :x2="95 + 85 * (index - 1)" y2="68" stroke="goldenrod" />
|
|
120
129
|
<line :x1="25 + 85 * (index - 1)" y1="92" :x2="95 + 85 * (index - 1)" y2="92" stroke="goldenrod" />
|
|
121
130
|
|
|
@@ -153,7 +162,7 @@
|
|
|
153
162
|
</g>
|
|
154
163
|
</template>
|
|
155
164
|
|
|
156
|
-
<template v-if="!isIndiaResourceMarket">
|
|
165
|
+
<template v-if="!isIndiaResourceMarket && !isNinePriceMarket">
|
|
157
166
|
<rect width="30" height="30" x="705" y="45" rx="2" fill="darkgoldenrod" />
|
|
158
167
|
<circle r="10" cx="732" cy="48" fill="yellow" />
|
|
159
168
|
<text
|
|
@@ -227,15 +236,43 @@
|
|
|
227
236
|
<Coal :pieceId="-1" :targetState="{ x: 858, y: 57 }" :canClick="false" :transparent="true" :scale="0.2" />
|
|
228
237
|
</template>
|
|
229
238
|
|
|
239
|
+
<!-- South Africa: coal storage pool below the market. Always-available $8
|
|
240
|
+
flat buy. Used coal returns here; market refills draw from here first. -->
|
|
241
|
+
<template v-if="coalStorage !== undefined">
|
|
242
|
+
<rect
|
|
243
|
+
width="180"
|
|
244
|
+
height="70"
|
|
245
|
+
x="795"
|
|
246
|
+
y="45"
|
|
247
|
+
rx="2"
|
|
248
|
+
fill="chocolate"
|
|
249
|
+
stroke="sandybrown"
|
|
250
|
+
stroke-width="4px"
|
|
251
|
+
/>
|
|
252
|
+
<circle r="10" cx="973" cy="45" fill="yellow" />
|
|
253
|
+
<text
|
|
254
|
+
text-anchor="middle"
|
|
255
|
+
style="font-size: 16px; font-family: monospace"
|
|
256
|
+
x="973"
|
|
257
|
+
y="45"
|
|
258
|
+
fill="darkgoldenrod"
|
|
259
|
+
>
|
|
260
|
+
8
|
|
261
|
+
</text>
|
|
262
|
+
<text text-anchor="start" style="font-size: 11px; font-family: monospace" x="800" y="42" fill="black">
|
|
263
|
+
Coal storage
|
|
264
|
+
</text>
|
|
265
|
+
</template>
|
|
266
|
+
|
|
230
267
|
<template v-for="coal in coals">
|
|
231
268
|
<Coal
|
|
232
269
|
:key="coal.id"
|
|
233
270
|
:pieceId="coal.id"
|
|
234
271
|
:targetState="{ x: coal.x, y: coal.y }"
|
|
235
|
-
:canClick="!coal.transparent && canBuyResource('coal', coal.side)"
|
|
272
|
+
:canClick="!coal.transparent && canBuyResource('coal', coal.side, coal.fromStorage)"
|
|
236
273
|
:transparent="coal.transparent"
|
|
237
274
|
:scale="isIndiaResourceMarket ? 0.06 : 0.08"
|
|
238
|
-
@click="buyResource('coal', coal.side)"
|
|
275
|
+
@click="buyResource('coal', coal.side, coal.fromStorage)"
|
|
239
276
|
/>
|
|
240
277
|
</template>
|
|
241
278
|
|
|
@@ -344,7 +381,10 @@ export default class Resources extends Vue {
|
|
|
344
381
|
@Prop() isMiddleEast?: boolean;
|
|
345
382
|
@Prop() isIndiaResourceMarket?: boolean;
|
|
346
383
|
@Prop() availableSurplusOil?: number;
|
|
347
|
-
@Prop() buyableResources?: { resource: string, side?: 'north' | 'south' }[];
|
|
384
|
+
@Prop() buyableResources?: { resource: string, side?: 'north' | 'south', fromStorage?: boolean }[];
|
|
385
|
+
// South Africa: number of coal cubes in the storage pool below the market.
|
|
386
|
+
// Undefined on all other maps (panel is hidden).
|
|
387
|
+
@Prop() coalStorage?: number;
|
|
348
388
|
|
|
349
389
|
@Inject() preferences!: Preferences;
|
|
350
390
|
|
|
@@ -354,25 +394,27 @@ export default class Resources extends Vue {
|
|
|
354
394
|
uraniums: Piece[] = [];
|
|
355
395
|
|
|
356
396
|
isKorea: boolean = false;
|
|
397
|
+
isNinePriceMarket: boolean = false;
|
|
357
398
|
coalsNorth: Piece[] = [];
|
|
358
399
|
oilsNorth: Piece[] = [];
|
|
359
400
|
garbagesNorth: Piece[] = [];
|
|
360
401
|
|
|
361
|
-
//
|
|
402
|
+
// Lay out cubes within price spaces ($1..$maxPrice) using the prices array
|
|
362
403
|
// to determine slot count per space. Cubes are indexed cheap→expensive in the
|
|
363
404
|
// prices array, so the cheap end empties as the market depletes.
|
|
364
|
-
// Skips entries with price >
|
|
365
|
-
//
|
|
366
|
-
private
|
|
405
|
+
// Skips entries with price > maxPrice — used for Korea uranium corners ($10..$16),
|
|
406
|
+
// which the caller renders separately. Europe maxPrice=9 (no corners).
|
|
407
|
+
private buildMainRowPieces(
|
|
367
408
|
prices: number[],
|
|
368
409
|
market: number,
|
|
369
410
|
idPrefix: string,
|
|
370
|
-
side: 'north' | 'south',
|
|
371
411
|
y: number,
|
|
412
|
+
options: { side?: 'north' | 'south'; maxPrice?: number } = {},
|
|
372
413
|
): Piece[] {
|
|
414
|
+
const maxPrice = options.maxPrice ?? 8;
|
|
373
415
|
const groups: { price: number; slots: number; firstIdx: number }[] = [];
|
|
374
416
|
prices.forEach((p, idx) => {
|
|
375
|
-
if (p >
|
|
417
|
+
if (p > maxPrice) return;
|
|
376
418
|
const last = groups[groups.length - 1];
|
|
377
419
|
if (last && last.price === p) last.slots++;
|
|
378
420
|
else groups.push({ price: p, slots: 1, firstIdx: idx });
|
|
@@ -394,7 +436,7 @@ export default class Resources extends Vue {
|
|
|
394
436
|
x,
|
|
395
437
|
y,
|
|
396
438
|
transparent: i < (prices.length - market),
|
|
397
|
-
side,
|
|
439
|
+
side: options.side,
|
|
398
440
|
});
|
|
399
441
|
}
|
|
400
442
|
});
|
|
@@ -409,14 +451,14 @@ export default class Resources extends Vue {
|
|
|
409
451
|
|
|
410
452
|
if (isKorea) {
|
|
411
453
|
// SOUTH market — main row coal/oil/garbage at the existing y positions.
|
|
412
|
-
this.coals = this.
|
|
413
|
-
gameState.coalPrices!, gameState.coalMarket, 'coal', 'south'
|
|
454
|
+
this.coals = this.buildMainRowPieces(
|
|
455
|
+
gameState.coalPrices!, gameState.coalMarket, 'coal', 48, { side: 'south' },
|
|
414
456
|
);
|
|
415
|
-
this.oils = this.
|
|
416
|
-
gameState.oilPrices!, gameState.oilMarket, 'oil', 'south'
|
|
457
|
+
this.oils = this.buildMainRowPieces(
|
|
458
|
+
gameState.oilPrices!, gameState.oilMarket, 'oil', 70, { side: 'south' },
|
|
417
459
|
);
|
|
418
|
-
this.garbages = this.
|
|
419
|
-
gameState.garbagePrices!, gameState.garbageMarket, 'garbage', 'south'
|
|
460
|
+
this.garbages = this.buildMainRowPieces(
|
|
461
|
+
gameState.garbagePrices!, gameState.garbageMarket, 'garbage', 94, { side: 'south' },
|
|
420
462
|
);
|
|
421
463
|
|
|
422
464
|
// SOUTH uranium: $1..$8 sit between the oil (y=70) and garbage (y=94) rows
|
|
@@ -446,19 +488,41 @@ export default class Resources extends Vue {
|
|
|
446
488
|
|
|
447
489
|
// NORTH market — placed above the South so its rect sits at y=-100..-20.
|
|
448
490
|
// Cube rows match the South layout's offsets within the rect (top/mid/bottom).
|
|
449
|
-
this.coalsNorth = this.
|
|
450
|
-
gameState.coalPricesNorth!, gameState.coalMarketNorth!, 'coal_north', 'north'
|
|
491
|
+
this.coalsNorth = this.buildMainRowPieces(
|
|
492
|
+
gameState.coalPricesNorth!, gameState.coalMarketNorth!, 'coal_north', -92, { side: 'north' },
|
|
451
493
|
);
|
|
452
|
-
this.oilsNorth = this.
|
|
453
|
-
gameState.oilPricesNorth!, gameState.oilMarketNorth!, 'oil_north', 'north'
|
|
494
|
+
this.oilsNorth = this.buildMainRowPieces(
|
|
495
|
+
gameState.oilPricesNorth!, gameState.oilMarketNorth!, 'oil_north', -70, { side: 'north' },
|
|
454
496
|
);
|
|
455
|
-
this.garbagesNorth = this.
|
|
456
|
-
gameState.garbagePricesNorth!, gameState.garbageMarketNorth!, 'garbage_north', 'north'
|
|
497
|
+
this.garbagesNorth = this.buildMainRowPieces(
|
|
498
|
+
gameState.garbagePricesNorth!, gameState.garbageMarketNorth!, 'garbage_north', -46, { side: 'north' },
|
|
457
499
|
);
|
|
458
500
|
|
|
459
501
|
return;
|
|
460
502
|
}
|
|
461
503
|
|
|
504
|
+
// Europe and North America: 9 price spaces ($1..$9), per-price slot count
|
|
505
|
+
// varies by resource, and uranium tops out at $9 (no $10/$12/$14/$16 corner).
|
|
506
|
+
// Rows are spaced apart (uranium between oil and garbage) so each cube
|
|
507
|
+
// is comfortably clickable: oil 70 → uranium 92 → garbage 106.
|
|
508
|
+
if (gameState.map?.name === 'Europe' || gameState.map?.name === 'North America') {
|
|
509
|
+
this.isNinePriceMarket = true;
|
|
510
|
+
this.coals = this.buildMainRowPieces(
|
|
511
|
+
gameState.coalPrices!, gameState.coalMarket, 'coal', 48, { maxPrice: 9 },
|
|
512
|
+
);
|
|
513
|
+
this.oils = this.buildMainRowPieces(
|
|
514
|
+
gameState.oilPrices!, gameState.oilMarket, 'oil', 70, { maxPrice: 9 },
|
|
515
|
+
);
|
|
516
|
+
this.uraniums = this.buildMainRowPieces(
|
|
517
|
+
gameState.uraniumPrices!, gameState.uraniumMarket, 'uranium', 92, { maxPrice: 9 },
|
|
518
|
+
);
|
|
519
|
+
this.garbages = this.buildMainRowPieces(
|
|
520
|
+
gameState.garbagePrices!, gameState.garbageMarket, 'garbage', 106, { maxPrice: 9 },
|
|
521
|
+
);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
this.isNinePriceMarket = false;
|
|
525
|
+
|
|
462
526
|
// Non-Korea: original logic below (unchanged).
|
|
463
527
|
this.coalsNorth = [];
|
|
464
528
|
this.oilsNorth = [];
|
|
@@ -522,6 +586,19 @@ export default class Resources extends Vue {
|
|
|
522
586
|
});
|
|
523
587
|
}
|
|
524
588
|
|
|
589
|
+
// South Africa: storage pool cubes. Always $8, clickable when buyable.
|
|
590
|
+
if (gameState.coalStorage !== undefined) {
|
|
591
|
+
range(gameState.coalStorage).forEach((i) => {
|
|
592
|
+
this.coals.push({
|
|
593
|
+
id: 'coal_storage_' + i,
|
|
594
|
+
x: 800 + (i % 8) * 20 + (i >= 8 && i < 16 ? 10 : 0),
|
|
595
|
+
y: 50 + Math.floor(i / 8) * 20,
|
|
596
|
+
transparent: false,
|
|
597
|
+
fromStorage: true,
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
525
602
|
this.oils = [];
|
|
526
603
|
if (gameState.map?.name == 'Middle East') {
|
|
527
604
|
let maxRegularOil = gameState.oilPrices!.filter(p => p > 1).length;
|
|
@@ -635,8 +712,10 @@ export default class Resources extends Vue {
|
|
|
635
712
|
}
|
|
636
713
|
}
|
|
637
714
|
|
|
638
|
-
canBuyResource(resource: string, side?: 'north' | 'south') {
|
|
639
|
-
return !!this.buyableResources!.find(
|
|
715
|
+
canBuyResource(resource: string, side?: 'north' | 'south', fromStorage?: boolean) {
|
|
716
|
+
return !!this.buyableResources!.find(
|
|
717
|
+
(r) => r.resource == resource && r.side == side && !!r.fromStorage == !!fromStorage,
|
|
718
|
+
);
|
|
640
719
|
}
|
|
641
720
|
|
|
642
721
|
get hasBuyableNorth() {
|
|
@@ -647,8 +726,8 @@ export default class Resources extends Vue {
|
|
|
647
726
|
return !!this.buyableResources?.some(r => r.side === 'south');
|
|
648
727
|
}
|
|
649
728
|
|
|
650
|
-
buyResource(resource: string, side?: 'north' | 'south') {
|
|
651
|
-
this.$emit('buyResource', { resource, side });
|
|
729
|
+
buyResource(resource: string, side?: 'north' | 'south', fromStorage?: boolean) {
|
|
730
|
+
this.$emit('buyResource', { resource, side, fromStorage });
|
|
652
731
|
}
|
|
653
732
|
}
|
|
654
733
|
</script>
|
package/src/self-contained.ts
CHANGED
|
@@ -10,30 +10,31 @@ function launchSelfContained(selector = '#app') {
|
|
|
10
10
|
|
|
11
11
|
const emitter = launch(selector);
|
|
12
12
|
|
|
13
|
-
let gameState = setup(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
13
|
+
let gameState = setup(
|
|
14
|
+
5,
|
|
15
|
+
{ map: 'Baden-Württemberg', variant: 'original', showMoney: true, randomizeMap: false },
|
|
16
|
+
'7'
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// Dev coord-picker example (commented; uncomment + drop a board photo into
|
|
20
|
+
// viewer/public/ to author city coordinates for a new map). See the picker
|
|
21
|
+
// notes in CLAUDE/memory or in the engine map-authoring docs.
|
|
22
|
+
//
|
|
23
|
+
// import { map as fullMap } from 'powergrid-engine/src/maps/southafrica';
|
|
24
|
+
// gameState.map.cities = fullMap.cities.map((c) => ({ ...c }));
|
|
25
|
+
// gameState.map.connections = fullMap.connections.map((c) => ({ ...c, nodes: [...c.nodes] }));
|
|
26
|
+
// gameState.map.devBackdrop = { src: '/southafrica.jpg', width: 1200, height: 863, opacity: 0.5 };
|
|
25
27
|
// gameState.map.adjustRatio = [1, 1];
|
|
26
|
-
// gameState.map.
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
// gameState.map.
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// }));
|
|
28
|
+
// gameState.map.mapRotation = 0;
|
|
29
|
+
// gameState.map.mapPosition = [0, 0];
|
|
30
|
+
// gameState.map.viewBox = [1200, 863];
|
|
31
|
+
// gameState.map.playerBoardsPosition = [-9999, -9999];
|
|
32
|
+
// gameState.map.powerPlantMarketPosition = [-9999, -9999];
|
|
33
|
+
// gameState.map.buttonsPosition = [-9999, -9999];
|
|
34
|
+
// gameState.map.supplyPosition = [-9999, -9999];
|
|
35
|
+
// gameState.map.roundInfoPosition = [-9999, -9999];
|
|
36
|
+
// gameState.map.cityCountPosition = [-9999, -9999];
|
|
37
|
+
// gameState.map.playerOrderPosition = [-9999, -9999];
|
|
37
38
|
|
|
38
39
|
for (let i = 0; i < gameState.players.length; i++) {
|
|
39
40
|
gameState.players[i].name = `Player ${i + 1}`;
|
package/src/types/ui-data.ts
CHANGED
|
@@ -22,6 +22,9 @@ export interface Piece {
|
|
|
22
22
|
powerPlant?: PowerPlant;
|
|
23
23
|
transparent?: boolean;
|
|
24
24
|
side?: 'north' | 'south';
|
|
25
|
+
// South Africa: this coal cube sits in the storage pool below the market.
|
|
26
|
+
// Clicking emits buyResource with fromStorage:true (flat $8 buy).
|
|
27
|
+
fromStorage?: boolean;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export enum PieceType {
|