powergrid-viewer 1.6.0 → 1.8.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.8.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.12.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/assert": "^1.4.7",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"public/audio"
|
|
51
51
|
],
|
|
52
52
|
"scripts": {
|
|
53
|
-
"serve": "vue-cli-service serve",
|
|
54
|
-
"build": "vue-cli-service build",
|
|
53
|
+
"serve": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
|
|
54
|
+
"build": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
|
|
55
55
|
"test:unit": "vue-cli-service test:unit",
|
|
56
56
|
"package": "vue-cli-service build --target lib --name powergrid-viewer src/wrapper.ts"
|
|
57
57
|
}
|
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
|
/>
|
|
@@ -287,6 +289,9 @@
|
|
|
287
289
|
<li><strong>One</strong> player per city</li>
|
|
288
290
|
<li>
|
|
289
291
|
Resource Resupply: <strong>{{ G.resourceResupply[0] }}</strong>
|
|
292
|
+
<template v-if="G.resourceResupplyNorth">
|
|
293
|
+
(S), <strong>{{ G.resourceResupplyNorth[0] }}</strong> (N)
|
|
294
|
+
</template>
|
|
290
295
|
</li>
|
|
291
296
|
<li>Bureaucracy: remove <strong>highest</strong> power plant from market</li>
|
|
292
297
|
</ul>
|
|
@@ -301,6 +306,9 @@
|
|
|
301
306
|
<li><strong>Two</strong> players per city</li>
|
|
302
307
|
<li>
|
|
303
308
|
Resource Resupply: <strong>{{ G.resourceResupply[1] }}</strong>
|
|
309
|
+
<template v-if="G.resourceResupplyNorth">
|
|
310
|
+
(S), <strong>{{ G.resourceResupplyNorth[1] }}</strong> (N)
|
|
311
|
+
</template>
|
|
304
312
|
</li>
|
|
305
313
|
<li>Bureaucracy: remove <strong>highest</strong> power plant from market</li>
|
|
306
314
|
</ul>
|
|
@@ -312,6 +320,9 @@
|
|
|
312
320
|
<li><strong>Three</strong> players per city</li>
|
|
313
321
|
<li>
|
|
314
322
|
Resource Resupply: <strong>{{ G.resourceResupply[2] }}</strong>
|
|
323
|
+
<template v-if="G.resourceResupplyNorth">
|
|
324
|
+
(S), <strong>{{ G.resourceResupplyNorth[2] }}</strong> (N)
|
|
325
|
+
</template>
|
|
315
326
|
</li>
|
|
316
327
|
<li>Bureaucracy: remove <strong>lowest</strong> power plant from market</li>
|
|
317
328
|
<li>All power plants available for auction</li>
|
|
@@ -329,7 +340,7 @@
|
|
|
329
340
|
<br />
|
|
330
341
|
<div>
|
|
331
342
|
<strong>Map Specific Rules:</strong><br />
|
|
332
|
-
<span style="white-space: pre">{{ G.map.mapSpecificRules }}</span>
|
|
343
|
+
<span style="white-space: pre-wrap">{{ G.map.mapSpecificRules }}</span>
|
|
333
344
|
</div>
|
|
334
345
|
</template>
|
|
335
346
|
<br />
|
|
@@ -621,8 +632,15 @@ export default class Game extends Vue {
|
|
|
621
632
|
}
|
|
622
633
|
}
|
|
623
634
|
|
|
624
|
-
buyResource(resource: ResourceType) {
|
|
625
|
-
|
|
635
|
+
buyResource(payload: { resource: ResourceType, side?: 'north' | 'south', fromStorage?: boolean }) {
|
|
636
|
+
const data: { resource: ResourceType, side?: 'north' | 'south', fromStorage?: boolean } = { resource: payload.resource };
|
|
637
|
+
if (payload.side) {
|
|
638
|
+
data.side = payload.side;
|
|
639
|
+
}
|
|
640
|
+
if (payload.fromStorage) {
|
|
641
|
+
data.fromStorage = payload.fromStorage;
|
|
642
|
+
}
|
|
643
|
+
this.sendMove({ name: MoveName.BuyResource, data });
|
|
626
644
|
}
|
|
627
645
|
|
|
628
646
|
bid(bid: number) {
|
|
@@ -862,13 +880,13 @@ export default class Game extends Vue {
|
|
|
862
880
|
return !!availableMoves[MoveName.ChoosePowerPlant];
|
|
863
881
|
}
|
|
864
882
|
|
|
865
|
-
buyableResources() {
|
|
883
|
+
buyableResources(): { resource: ResourceType, side?: 'north' | 'south', fromStorage?: boolean }[] {
|
|
866
884
|
if (!this.canMove()) return [];
|
|
867
885
|
|
|
868
886
|
const currentPlayer = this.G!.players[this.player!];
|
|
869
887
|
const availableMoves = currentPlayer.availableMoves!;
|
|
870
888
|
|
|
871
|
-
return
|
|
889
|
+
return availableMoves[MoveName.BuyResource] || [];
|
|
872
890
|
}
|
|
873
891
|
|
|
874
892
|
canBuyResource(resource?: ResourceType) {
|
|
@@ -1084,6 +1102,19 @@ export default class Game extends Vue {
|
|
|
1084
1102
|
return playersSortedByScore(this.G!);
|
|
1085
1103
|
}
|
|
1086
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
|
+
|
|
1087
1118
|
get adjustedPlayerOrder() {
|
|
1088
1119
|
if (!this.G) {
|
|
1089
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">
|
|
@@ -132,6 +164,7 @@ export default class Map extends Vue {
|
|
|
132
164
|
@Prop() connections?: Connection[];
|
|
133
165
|
@Prop() playerColors?: string[];
|
|
134
166
|
@Prop() buildableCities?: string[];
|
|
167
|
+
@Prop() devBackdrop?: { src: string; width: number; height: number; opacity?: number };
|
|
135
168
|
|
|
136
169
|
@Inject() preferences!: Preferences;
|
|
137
170
|
|
|
@@ -192,5 +225,15 @@ export default class Map extends Vue {
|
|
|
192
225
|
build(city: City) {
|
|
193
226
|
this.$emit('build', city);
|
|
194
227
|
}
|
|
228
|
+
|
|
229
|
+
onPickCoord(e: MouseEvent) {
|
|
230
|
+
if (!this.devBackdrop) return;
|
|
231
|
+
const g = e.currentTarget as SVGGElement;
|
|
232
|
+
const ctm = g.getScreenCTM();
|
|
233
|
+
if (!ctm) return;
|
|
234
|
+
const pt = new DOMPoint(e.clientX, e.clientY).matrixTransform(ctm.inverse());
|
|
235
|
+
// eslint-disable-next-line no-console
|
|
236
|
+
console.log(`{ name: Cities.TODO, region: Regions.TODO, x: ${Math.round(pt.x)}, y: ${Math.round(pt.y)} },`);
|
|
237
|
+
}
|
|
195
238
|
}
|
|
196
239
|
</script>
|
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<g>
|
|
3
|
+
<!-- Korea: North resource market (above the standard market block).
|
|
4
|
+
The South market re-uses the existing rendering below; Korea-specific
|
|
5
|
+
cube positions are populated in createPieces. -->
|
|
6
|
+
<template v-if="isKorea">
|
|
7
|
+
<text x="20" y="-110" font-weight="700" fill="black" style="font-size: 22px">North Market</text>
|
|
8
|
+
<rect width="760" height="80" x="20" y="-100" rx="3" fill="#c89c3a" />
|
|
9
|
+
<template v-for="index in 8">
|
|
10
|
+
<rect
|
|
11
|
+
:key="'north_resources' + index"
|
|
12
|
+
width="70"
|
|
13
|
+
height="70"
|
|
14
|
+
:x="25 + 85 * (index - 1)"
|
|
15
|
+
y="-95"
|
|
16
|
+
rx="2"
|
|
17
|
+
fill="darkgoldenrod"
|
|
18
|
+
/>
|
|
19
|
+
<circle
|
|
20
|
+
:key="'north_resourcesCircle' + index"
|
|
21
|
+
r="10"
|
|
22
|
+
:cx="92 + 85 * (index - 1)"
|
|
23
|
+
cy="-92"
|
|
24
|
+
fill="yellow"
|
|
25
|
+
/>
|
|
26
|
+
<text
|
|
27
|
+
:key="'north_resourcesText' + index"
|
|
28
|
+
text-anchor="middle"
|
|
29
|
+
style="font-size: 16px; font-family: monospace"
|
|
30
|
+
:x="92 + 85 * (index - 1)"
|
|
31
|
+
y="-92"
|
|
32
|
+
fill="darkgoldenrod"
|
|
33
|
+
>
|
|
34
|
+
{{ index }}
|
|
35
|
+
</text>
|
|
36
|
+
</template>
|
|
37
|
+
<template v-for="coal in coalsNorth">
|
|
38
|
+
<Coal
|
|
39
|
+
:key="coal.id"
|
|
40
|
+
:pieceId="coal.id"
|
|
41
|
+
:targetState="{ x: coal.x, y: coal.y }"
|
|
42
|
+
:canClick="!coal.transparent && canBuyResource('coal', coal.side, coal.fromStorage)"
|
|
43
|
+
:transparent="coal.transparent"
|
|
44
|
+
:scale="0.08"
|
|
45
|
+
@click="buyResource('coal', coal.side, coal.fromStorage)"
|
|
46
|
+
/>
|
|
47
|
+
</template>
|
|
48
|
+
<template v-for="oil in oilsNorth">
|
|
49
|
+
<Oil
|
|
50
|
+
:key="oil.id"
|
|
51
|
+
:pieceId="oil.id"
|
|
52
|
+
:targetState="{ x: oil.x, y: oil.y }"
|
|
53
|
+
:canClick="!oil.transparent && canBuyResource('oil', oil.side)"
|
|
54
|
+
:transparent="oil.transparent"
|
|
55
|
+
@click="buyResource('oil', oil.side)"
|
|
56
|
+
/>
|
|
57
|
+
</template>
|
|
58
|
+
<template v-for="garbage in garbagesNorth">
|
|
59
|
+
<Garbage
|
|
60
|
+
:key="garbage.id"
|
|
61
|
+
:pieceId="garbage.id"
|
|
62
|
+
:targetState="{ x: garbage.x, y: garbage.y }"
|
|
63
|
+
:canClick="!garbage.transparent && canBuyResource('garbage', garbage.side)"
|
|
64
|
+
:transparent="garbage.transparent"
|
|
65
|
+
@click="buyResource('garbage', garbage.side)"
|
|
66
|
+
/>
|
|
67
|
+
</template>
|
|
68
|
+
<text x="20" y="35" font-weight="700" fill="black" style="font-size: 22px">South Market</text>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
3
71
|
<text v-if="resourceResupply[0] < 10" x="30" y="20" font-weight="600" fill="black" style="font-size: 24px"
|
|
4
72
|
>Resource Resupply:</text
|
|
5
73
|
>
|
|
@@ -24,9 +92,18 @@
|
|
|
24
92
|
</text>
|
|
25
93
|
<Uranium :pieceId="-1" :targetState="{ x: 428, y: 12 }" :canClick="false" :transparent="false" />
|
|
26
94
|
|
|
27
|
-
<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" />
|
|
28
105
|
<rect v-if="isIndiaResourceMarket" width="680" height="80" x="20" y="40" rx="3" fill="goldenrod" />
|
|
29
|
-
<template v-for="index in 8">
|
|
106
|
+
<template v-for="index in isNinePriceMarket ? 9 : 8">
|
|
30
107
|
<rect
|
|
31
108
|
:key="'resources' + index"
|
|
32
109
|
width="70"
|
|
@@ -47,7 +124,7 @@
|
|
|
47
124
|
>
|
|
48
125
|
{{ index }}
|
|
49
126
|
</text>
|
|
50
|
-
<g :key="'lines' + index" v-if="!isIndiaResourceMarket">
|
|
127
|
+
<g :key="'lines' + index" v-if="!isIndiaResourceMarket && !isNinePriceMarket">
|
|
51
128
|
<line :x1="25 + 85 * (index - 1)" y1="68" :x2="95 + 85 * (index - 1)" y2="68" stroke="goldenrod" />
|
|
52
129
|
<line :x1="25 + 85 * (index - 1)" y1="92" :x2="95 + 85 * (index - 1)" y2="92" stroke="goldenrod" />
|
|
53
130
|
|
|
@@ -85,7 +162,7 @@
|
|
|
85
162
|
</g>
|
|
86
163
|
</template>
|
|
87
164
|
|
|
88
|
-
<template v-if="!isIndiaResourceMarket">
|
|
165
|
+
<template v-if="!isIndiaResourceMarket && !isNinePriceMarket">
|
|
89
166
|
<rect width="30" height="30" x="705" y="45" rx="2" fill="darkgoldenrod" />
|
|
90
167
|
<circle r="10" cx="732" cy="48" fill="yellow" />
|
|
91
168
|
<text
|
|
@@ -159,15 +236,43 @@
|
|
|
159
236
|
<Coal :pieceId="-1" :targetState="{ x: 858, y: 57 }" :canClick="false" :transparent="true" :scale="0.2" />
|
|
160
237
|
</template>
|
|
161
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
|
+
|
|
162
267
|
<template v-for="coal in coals">
|
|
163
268
|
<Coal
|
|
164
269
|
:key="coal.id"
|
|
165
270
|
:pieceId="coal.id"
|
|
166
271
|
:targetState="{ x: coal.x, y: coal.y }"
|
|
167
|
-
:canClick="!coal.transparent && canBuyResource('coal')"
|
|
272
|
+
:canClick="!coal.transparent && canBuyResource('coal', coal.side, coal.fromStorage)"
|
|
168
273
|
:transparent="coal.transparent"
|
|
169
274
|
:scale="isIndiaResourceMarket ? 0.06 : 0.08"
|
|
170
|
-
@click="buyResource('coal')"
|
|
275
|
+
@click="buyResource('coal', coal.side, coal.fromStorage)"
|
|
171
276
|
/>
|
|
172
277
|
</template>
|
|
173
278
|
|
|
@@ -176,9 +281,9 @@
|
|
|
176
281
|
:key="oil.id"
|
|
177
282
|
:pieceId="oil.id"
|
|
178
283
|
:targetState="{ x: oil.x, y: oil.y }"
|
|
179
|
-
:canClick="!oil.transparent && canBuyResource('oil')"
|
|
284
|
+
:canClick="!oil.transparent && canBuyResource('oil', oil.side)"
|
|
180
285
|
:transparent="oil.transparent"
|
|
181
|
-
@click="buyResource('oil')"
|
|
286
|
+
@click="buyResource('oil', oil.side)"
|
|
182
287
|
/>
|
|
183
288
|
</template>
|
|
184
289
|
|
|
@@ -187,10 +292,10 @@
|
|
|
187
292
|
:key="garbage.id"
|
|
188
293
|
:pieceId="garbage.id"
|
|
189
294
|
:targetState="{ x: garbage.x, y: garbage.y }"
|
|
190
|
-
:canClick="!garbage.transparent && canBuyResource('garbage')"
|
|
295
|
+
:canClick="!garbage.transparent && canBuyResource('garbage', garbage.side)"
|
|
191
296
|
:transparent="garbage.transparent"
|
|
192
297
|
:scale="isIndiaResourceMarket ? 0.8 : 1"
|
|
193
|
-
@click="buyResource('garbage')"
|
|
298
|
+
@click="buyResource('garbage', garbage.side)"
|
|
194
299
|
/>
|
|
195
300
|
</template>
|
|
196
301
|
|
|
@@ -199,9 +304,9 @@
|
|
|
199
304
|
:key="uranium.id"
|
|
200
305
|
:pieceId="uranium.id"
|
|
201
306
|
:targetState="{ x: uranium.x, y: uranium.y }"
|
|
202
|
-
:canClick="!uranium.transparent && canBuyResource('uranium')"
|
|
307
|
+
:canClick="!uranium.transparent && canBuyResource('uranium', uranium.side)"
|
|
203
308
|
:transparent="uranium.transparent"
|
|
204
|
-
@click="buyResource('uranium')"
|
|
309
|
+
@click="buyResource('uranium', uranium.side)"
|
|
205
310
|
/>
|
|
206
311
|
</template>
|
|
207
312
|
|
|
@@ -222,7 +327,29 @@
|
|
|
222
327
|
|
|
223
328
|
<template v-if="!preferences.disableHelp">
|
|
224
329
|
<rect
|
|
225
|
-
v-if="buyableResources.length > 0"
|
|
330
|
+
v-if="!isKorea && buyableResources.length > 0"
|
|
331
|
+
x="15"
|
|
332
|
+
y="35"
|
|
333
|
+
width="770"
|
|
334
|
+
height="90"
|
|
335
|
+
rx="2"
|
|
336
|
+
fill="none"
|
|
337
|
+
stroke="blue"
|
|
338
|
+
stroke-width="2px"
|
|
339
|
+
/>
|
|
340
|
+
<rect
|
|
341
|
+
v-if="isKorea && hasBuyableNorth"
|
|
342
|
+
x="15"
|
|
343
|
+
y="-105"
|
|
344
|
+
width="770"
|
|
345
|
+
height="90"
|
|
346
|
+
rx="2"
|
|
347
|
+
fill="none"
|
|
348
|
+
stroke="blue"
|
|
349
|
+
stroke-width="2px"
|
|
350
|
+
/>
|
|
351
|
+
<rect
|
|
352
|
+
v-if="isKorea && hasBuyableSouth"
|
|
226
353
|
x="15"
|
|
227
354
|
y="35"
|
|
228
355
|
width="770"
|
|
@@ -254,7 +381,10 @@ export default class Resources extends Vue {
|
|
|
254
381
|
@Prop() isMiddleEast?: boolean;
|
|
255
382
|
@Prop() isIndiaResourceMarket?: boolean;
|
|
256
383
|
@Prop() availableSurplusOil?: number;
|
|
257
|
-
@Prop() buyableResources?: string[];
|
|
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;
|
|
258
388
|
|
|
259
389
|
@Inject() preferences!: Preferences;
|
|
260
390
|
|
|
@@ -263,8 +393,142 @@ export default class Resources extends Vue {
|
|
|
263
393
|
garbages: Piece[] = [];
|
|
264
394
|
uraniums: Piece[] = [];
|
|
265
395
|
|
|
396
|
+
isKorea: boolean = false;
|
|
397
|
+
isNinePriceMarket: boolean = false;
|
|
398
|
+
coalsNorth: Piece[] = [];
|
|
399
|
+
oilsNorth: Piece[] = [];
|
|
400
|
+
garbagesNorth: Piece[] = [];
|
|
401
|
+
|
|
402
|
+
// Lay out cubes within price spaces ($1..$maxPrice) using the prices array
|
|
403
|
+
// to determine slot count per space. Cubes are indexed cheap→expensive in the
|
|
404
|
+
// prices array, so the cheap end empties as the market depletes.
|
|
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(
|
|
408
|
+
prices: number[],
|
|
409
|
+
market: number,
|
|
410
|
+
idPrefix: string,
|
|
411
|
+
y: number,
|
|
412
|
+
options: { side?: 'north' | 'south'; maxPrice?: number } = {},
|
|
413
|
+
): Piece[] {
|
|
414
|
+
const maxPrice = options.maxPrice ?? 8;
|
|
415
|
+
const groups: { price: number; slots: number; firstIdx: number }[] = [];
|
|
416
|
+
prices.forEach((p, idx) => {
|
|
417
|
+
if (p > maxPrice) return;
|
|
418
|
+
const last = groups[groups.length - 1];
|
|
419
|
+
if (last && last.price === p) last.slots++;
|
|
420
|
+
else groups.push({ price: p, slots: 1, firstIdx: idx });
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const pieces: Piece[] = [];
|
|
424
|
+
// Width within a price space across which cubes are distributed. Bigger
|
|
425
|
+
// value → cubes within the same price space appear farther apart.
|
|
426
|
+
const cubeAreaW = 64;
|
|
427
|
+
groups.forEach((g) => {
|
|
428
|
+
const psCenter = 60 + 85 * (g.price - 1);
|
|
429
|
+
for (let s = 0; s < g.slots; s++) {
|
|
430
|
+
const i = g.firstIdx + s;
|
|
431
|
+
const x = g.slots === 1
|
|
432
|
+
? psCenter
|
|
433
|
+
: psCenter - cubeAreaW / 2 + (cubeAreaW * (s + 0.5)) / g.slots;
|
|
434
|
+
pieces.push({
|
|
435
|
+
id: `${idPrefix}_${i}`,
|
|
436
|
+
x,
|
|
437
|
+
y,
|
|
438
|
+
transparent: i < (prices.length - market),
|
|
439
|
+
side: options.side,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
return pieces;
|
|
444
|
+
}
|
|
445
|
+
|
|
266
446
|
createPieces(gameState: GameState) {
|
|
267
|
-
if (gameState)
|
|
447
|
+
if (!gameState) return;
|
|
448
|
+
|
|
449
|
+
const isKorea = gameState.coalMarketNorth !== undefined;
|
|
450
|
+
this.isKorea = isKorea;
|
|
451
|
+
|
|
452
|
+
if (isKorea) {
|
|
453
|
+
// SOUTH market — main row coal/oil/garbage at the existing y positions.
|
|
454
|
+
this.coals = this.buildMainRowPieces(
|
|
455
|
+
gameState.coalPrices!, gameState.coalMarket, 'coal', 48, { side: 'south' },
|
|
456
|
+
);
|
|
457
|
+
this.oils = this.buildMainRowPieces(
|
|
458
|
+
gameState.oilPrices!, gameState.oilMarket, 'oil', 70, { side: 'south' },
|
|
459
|
+
);
|
|
460
|
+
this.garbages = this.buildMainRowPieces(
|
|
461
|
+
gameState.garbagePrices!, gameState.garbageMarket, 'garbage', 94, { side: 'south' },
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
// SOUTH uranium: $1..$8 sit between the oil (y=70) and garbage (y=94) rows
|
|
465
|
+
// so they don't visually overlap with oil. $10/$12/$14/$16 go in the corner
|
|
466
|
+
// 2x2 grid (top row at y=52, bottom row at y=91).
|
|
467
|
+
this.uraniums = [];
|
|
468
|
+
const uPrices = gameState.uraniumPrices!;
|
|
469
|
+
uPrices.forEach((p, i) => {
|
|
470
|
+
let x: number, y: number;
|
|
471
|
+
if (p <= 8) {
|
|
472
|
+
x = 60 + 85 * (p - 1);
|
|
473
|
+
y = 85;
|
|
474
|
+
} else {
|
|
475
|
+
// $10 → (710, 52), $12 → (750, 52), $14 → (710, 91), $16 → (750, 91)
|
|
476
|
+
const cornerX = (p === 10 || p === 14) ? 710 : 750;
|
|
477
|
+
const cornerY = (p === 10 || p === 12) ? 52 : 91;
|
|
478
|
+
x = cornerX;
|
|
479
|
+
y = cornerY;
|
|
480
|
+
}
|
|
481
|
+
this.uraniums.push({
|
|
482
|
+
id: `uranium_${i}`,
|
|
483
|
+
x, y,
|
|
484
|
+
transparent: i < (uPrices.length - gameState.uraniumMarket),
|
|
485
|
+
side: 'south',
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// NORTH market — placed above the South so its rect sits at y=-100..-20.
|
|
490
|
+
// Cube rows match the South layout's offsets within the rect (top/mid/bottom).
|
|
491
|
+
this.coalsNorth = this.buildMainRowPieces(
|
|
492
|
+
gameState.coalPricesNorth!, gameState.coalMarketNorth!, 'coal_north', -92, { side: 'north' },
|
|
493
|
+
);
|
|
494
|
+
this.oilsNorth = this.buildMainRowPieces(
|
|
495
|
+
gameState.oilPricesNorth!, gameState.oilMarketNorth!, 'oil_north', -70, { side: 'north' },
|
|
496
|
+
);
|
|
497
|
+
this.garbagesNorth = this.buildMainRowPieces(
|
|
498
|
+
gameState.garbagePricesNorth!, gameState.garbageMarketNorth!, 'garbage_north', -46, { side: 'north' },
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
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
|
+
|
|
526
|
+
// Non-Korea: original logic below (unchanged).
|
|
527
|
+
this.coalsNorth = [];
|
|
528
|
+
this.oilsNorth = [];
|
|
529
|
+
this.garbagesNorth = [];
|
|
530
|
+
|
|
531
|
+
{
|
|
268
532
|
this.coals = [];
|
|
269
533
|
if (gameState.map?.name == 'India') {
|
|
270
534
|
Array(24)
|
|
@@ -322,6 +586,19 @@ export default class Resources extends Vue {
|
|
|
322
586
|
});
|
|
323
587
|
}
|
|
324
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
|
+
|
|
325
602
|
this.oils = [];
|
|
326
603
|
if (gameState.map?.name == 'Middle East') {
|
|
327
604
|
let maxRegularOil = gameState.oilPrices!.filter(p => p > 1).length;
|
|
@@ -435,12 +712,22 @@ export default class Resources extends Vue {
|
|
|
435
712
|
}
|
|
436
713
|
}
|
|
437
714
|
|
|
438
|
-
canBuyResource(resource: string) {
|
|
439
|
-
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
|
+
);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
get hasBuyableNorth() {
|
|
722
|
+
return !!this.buyableResources?.some(r => r.side === 'north');
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
get hasBuyableSouth() {
|
|
726
|
+
return !!this.buyableResources?.some(r => r.side === 'south');
|
|
440
727
|
}
|
|
441
728
|
|
|
442
|
-
buyResource(resource: string) {
|
|
443
|
-
this.$emit('buyResource', resource);
|
|
729
|
+
buyResource(resource: string, side?: 'north' | 'south', fromStorage?: boolean) {
|
|
730
|
+
this.$emit('buyResource', { resource, side, fromStorage });
|
|
444
731
|
}
|
|
445
732
|
}
|
|
446
733
|
</script>
|