powergrid-viewer 1.6.0 → 1.7.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.6.0",
3
+ "version": "1.7.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.10.0"
21
+ "powergrid-engine": "1.11.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
  }
@@ -287,6 +287,9 @@
287
287
  <li><strong>One</strong> player per city</li>
288
288
  <li>
289
289
  Resource Resupply: <strong>{{ G.resourceResupply[0] }}</strong>
290
+ <template v-if="G.resourceResupplyNorth">
291
+ (S), <strong>{{ G.resourceResupplyNorth[0] }}</strong> (N)
292
+ </template>
290
293
  </li>
291
294
  <li>Bureaucracy: remove <strong>highest</strong> power plant from market</li>
292
295
  </ul>
@@ -301,6 +304,9 @@
301
304
  <li><strong>Two</strong> players per city</li>
302
305
  <li>
303
306
  Resource Resupply: <strong>{{ G.resourceResupply[1] }}</strong>
307
+ <template v-if="G.resourceResupplyNorth">
308
+ (S), <strong>{{ G.resourceResupplyNorth[1] }}</strong> (N)
309
+ </template>
304
310
  </li>
305
311
  <li>Bureaucracy: remove <strong>highest</strong> power plant from market</li>
306
312
  </ul>
@@ -312,6 +318,9 @@
312
318
  <li><strong>Three</strong> players per city</li>
313
319
  <li>
314
320
  Resource Resupply: <strong>{{ G.resourceResupply[2] }}</strong>
321
+ <template v-if="G.resourceResupplyNorth">
322
+ (S), <strong>{{ G.resourceResupplyNorth[2] }}</strong> (N)
323
+ </template>
315
324
  </li>
316
325
  <li>Bureaucracy: remove <strong>lowest</strong> power plant from market</li>
317
326
  <li>All power plants available for auction</li>
@@ -329,7 +338,7 @@
329
338
  <br />
330
339
  <div>
331
340
  <strong>Map Specific Rules:</strong><br />
332
- <span style="white-space: pre">{{ G.map.mapSpecificRules }}</span>
341
+ <span style="white-space: pre-wrap">{{ G.map.mapSpecificRules }}</span>
333
342
  </div>
334
343
  </template>
335
344
  <br />
@@ -621,8 +630,12 @@ export default class Game extends Vue {
621
630
  }
622
631
  }
623
632
 
624
- buyResource(resource: ResourceType) {
625
- this.sendMove({ name: MoveName.BuyResource, data: { resource } });
633
+ buyResource(payload: { resource: ResourceType, side?: 'north' | 'south' }) {
634
+ const data: { resource: ResourceType, side?: 'north' | 'south' } = { resource: payload.resource };
635
+ if (payload.side) {
636
+ data.side = payload.side;
637
+ }
638
+ this.sendMove({ name: MoveName.BuyResource, data });
626
639
  }
627
640
 
628
641
  bid(bid: number) {
@@ -862,13 +875,13 @@ export default class Game extends Vue {
862
875
  return !!availableMoves[MoveName.ChoosePowerPlant];
863
876
  }
864
877
 
865
- buyableResources() {
878
+ buyableResources(): { resource: ResourceType, side?: 'north' | 'south' }[] {
866
879
  if (!this.canMove()) return [];
867
880
 
868
881
  const currentPlayer = this.G!.players[this.player!];
869
882
  const availableMoves = currentPlayer.availableMoves!;
870
883
 
871
- return !!availableMoves[MoveName.BuyResource] && availableMoves[MoveName.BuyResource]!.map((m) => m.resource) || [];
884
+ return availableMoves[MoveName.BuyResource] || [];
872
885
  }
873
886
 
874
887
  canBuyResource(resource?: ResourceType) {
@@ -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)"
43
+ :transparent="coal.transparent"
44
+ :scale="0.08"
45
+ @click="buyResource('coal', coal.side)"
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
  >
@@ -164,10 +232,10 @@
164
232
  :key="coal.id"
165
233
  :pieceId="coal.id"
166
234
  :targetState="{ x: coal.x, y: coal.y }"
167
- :canClick="!coal.transparent && canBuyResource('coal')"
235
+ :canClick="!coal.transparent && canBuyResource('coal', coal.side)"
168
236
  :transparent="coal.transparent"
169
237
  :scale="isIndiaResourceMarket ? 0.06 : 0.08"
170
- @click="buyResource('coal')"
238
+ @click="buyResource('coal', coal.side)"
171
239
  />
172
240
  </template>
173
241
 
@@ -176,9 +244,9 @@
176
244
  :key="oil.id"
177
245
  :pieceId="oil.id"
178
246
  :targetState="{ x: oil.x, y: oil.y }"
179
- :canClick="!oil.transparent && canBuyResource('oil')"
247
+ :canClick="!oil.transparent && canBuyResource('oil', oil.side)"
180
248
  :transparent="oil.transparent"
181
- @click="buyResource('oil')"
249
+ @click="buyResource('oil', oil.side)"
182
250
  />
183
251
  </template>
184
252
 
@@ -187,10 +255,10 @@
187
255
  :key="garbage.id"
188
256
  :pieceId="garbage.id"
189
257
  :targetState="{ x: garbage.x, y: garbage.y }"
190
- :canClick="!garbage.transparent && canBuyResource('garbage')"
258
+ :canClick="!garbage.transparent && canBuyResource('garbage', garbage.side)"
191
259
  :transparent="garbage.transparent"
192
260
  :scale="isIndiaResourceMarket ? 0.8 : 1"
193
- @click="buyResource('garbage')"
261
+ @click="buyResource('garbage', garbage.side)"
194
262
  />
195
263
  </template>
196
264
 
@@ -199,9 +267,9 @@
199
267
  :key="uranium.id"
200
268
  :pieceId="uranium.id"
201
269
  :targetState="{ x: uranium.x, y: uranium.y }"
202
- :canClick="!uranium.transparent && canBuyResource('uranium')"
270
+ :canClick="!uranium.transparent && canBuyResource('uranium', uranium.side)"
203
271
  :transparent="uranium.transparent"
204
- @click="buyResource('uranium')"
272
+ @click="buyResource('uranium', uranium.side)"
205
273
  />
206
274
  </template>
207
275
 
@@ -222,7 +290,29 @@
222
290
 
223
291
  <template v-if="!preferences.disableHelp">
224
292
  <rect
225
- v-if="buyableResources.length > 0"
293
+ v-if="!isKorea && buyableResources.length > 0"
294
+ x="15"
295
+ y="35"
296
+ width="770"
297
+ height="90"
298
+ rx="2"
299
+ fill="none"
300
+ stroke="blue"
301
+ stroke-width="2px"
302
+ />
303
+ <rect
304
+ v-if="isKorea && hasBuyableNorth"
305
+ x="15"
306
+ y="-105"
307
+ width="770"
308
+ height="90"
309
+ rx="2"
310
+ fill="none"
311
+ stroke="blue"
312
+ stroke-width="2px"
313
+ />
314
+ <rect
315
+ v-if="isKorea && hasBuyableSouth"
226
316
  x="15"
227
317
  y="35"
228
318
  width="770"
@@ -254,7 +344,7 @@ export default class Resources extends Vue {
254
344
  @Prop() isMiddleEast?: boolean;
255
345
  @Prop() isIndiaResourceMarket?: boolean;
256
346
  @Prop() availableSurplusOil?: number;
257
- @Prop() buyableResources?: string[];
347
+ @Prop() buyableResources?: { resource: string, side?: 'north' | 'south' }[];
258
348
 
259
349
  @Inject() preferences!: Preferences;
260
350
 
@@ -263,8 +353,118 @@ export default class Resources extends Vue {
263
353
  garbages: Piece[] = [];
264
354
  uraniums: Piece[] = [];
265
355
 
356
+ isKorea: boolean = false;
357
+ coalsNorth: Piece[] = [];
358
+ oilsNorth: Piece[] = [];
359
+ garbagesNorth: Piece[] = [];
360
+
361
+ // Korea: lay out cubes within 8 price spaces ($1..$8) using the prices array
362
+ // to determine slot count per space. Cubes are indexed cheap→expensive in the
363
+ // prices array, so the cheap end empties as the market depletes.
364
+ // Skips entries with price > 8 (uranium corner $10/$12/$14/$16) — caller renders
365
+ // those separately.
366
+ private buildKoreaMainRowPieces(
367
+ prices: number[],
368
+ market: number,
369
+ idPrefix: string,
370
+ side: 'north' | 'south',
371
+ y: number,
372
+ ): Piece[] {
373
+ const groups: { price: number; slots: number; firstIdx: number }[] = [];
374
+ prices.forEach((p, idx) => {
375
+ if (p > 8) return;
376
+ const last = groups[groups.length - 1];
377
+ if (last && last.price === p) last.slots++;
378
+ else groups.push({ price: p, slots: 1, firstIdx: idx });
379
+ });
380
+
381
+ const pieces: Piece[] = [];
382
+ // Width within a price space across which cubes are distributed. Bigger
383
+ // value → cubes within the same price space appear farther apart.
384
+ const cubeAreaW = 64;
385
+ groups.forEach((g) => {
386
+ const psCenter = 60 + 85 * (g.price - 1);
387
+ for (let s = 0; s < g.slots; s++) {
388
+ const i = g.firstIdx + s;
389
+ const x = g.slots === 1
390
+ ? psCenter
391
+ : psCenter - cubeAreaW / 2 + (cubeAreaW * (s + 0.5)) / g.slots;
392
+ pieces.push({
393
+ id: `${idPrefix}_${i}`,
394
+ x,
395
+ y,
396
+ transparent: i < (prices.length - market),
397
+ side,
398
+ });
399
+ }
400
+ });
401
+ return pieces;
402
+ }
403
+
266
404
  createPieces(gameState: GameState) {
267
- if (gameState) {
405
+ if (!gameState) return;
406
+
407
+ const isKorea = gameState.coalMarketNorth !== undefined;
408
+ this.isKorea = isKorea;
409
+
410
+ if (isKorea) {
411
+ // SOUTH market — main row coal/oil/garbage at the existing y positions.
412
+ this.coals = this.buildKoreaMainRowPieces(
413
+ gameState.coalPrices!, gameState.coalMarket, 'coal', 'south', 48,
414
+ );
415
+ this.oils = this.buildKoreaMainRowPieces(
416
+ gameState.oilPrices!, gameState.oilMarket, 'oil', 'south', 70,
417
+ );
418
+ this.garbages = this.buildKoreaMainRowPieces(
419
+ gameState.garbagePrices!, gameState.garbageMarket, 'garbage', 'south', 94,
420
+ );
421
+
422
+ // SOUTH uranium: $1..$8 sit between the oil (y=70) and garbage (y=94) rows
423
+ // so they don't visually overlap with oil. $10/$12/$14/$16 go in the corner
424
+ // 2x2 grid (top row at y=52, bottom row at y=91).
425
+ this.uraniums = [];
426
+ const uPrices = gameState.uraniumPrices!;
427
+ uPrices.forEach((p, i) => {
428
+ let x: number, y: number;
429
+ if (p <= 8) {
430
+ x = 60 + 85 * (p - 1);
431
+ y = 85;
432
+ } else {
433
+ // $10 → (710, 52), $12 → (750, 52), $14 → (710, 91), $16 → (750, 91)
434
+ const cornerX = (p === 10 || p === 14) ? 710 : 750;
435
+ const cornerY = (p === 10 || p === 12) ? 52 : 91;
436
+ x = cornerX;
437
+ y = cornerY;
438
+ }
439
+ this.uraniums.push({
440
+ id: `uranium_${i}`,
441
+ x, y,
442
+ transparent: i < (uPrices.length - gameState.uraniumMarket),
443
+ side: 'south',
444
+ });
445
+ });
446
+
447
+ // NORTH market — placed above the South so its rect sits at y=-100..-20.
448
+ // Cube rows match the South layout's offsets within the rect (top/mid/bottom).
449
+ this.coalsNorth = this.buildKoreaMainRowPieces(
450
+ gameState.coalPricesNorth!, gameState.coalMarketNorth!, 'coal_north', 'north', -92,
451
+ );
452
+ this.oilsNorth = this.buildKoreaMainRowPieces(
453
+ gameState.oilPricesNorth!, gameState.oilMarketNorth!, 'oil_north', 'north', -70,
454
+ );
455
+ this.garbagesNorth = this.buildKoreaMainRowPieces(
456
+ gameState.garbagePricesNorth!, gameState.garbageMarketNorth!, 'garbage_north', 'north', -46,
457
+ );
458
+
459
+ return;
460
+ }
461
+
462
+ // Non-Korea: original logic below (unchanged).
463
+ this.coalsNorth = [];
464
+ this.oilsNorth = [];
465
+ this.garbagesNorth = [];
466
+
467
+ {
268
468
  this.coals = [];
269
469
  if (gameState.map?.name == 'India') {
270
470
  Array(24)
@@ -435,12 +635,20 @@ export default class Resources extends Vue {
435
635
  }
436
636
  }
437
637
 
438
- canBuyResource(resource: string) {
439
- return !!this.buyableResources!.find(r => r == resource);
638
+ canBuyResource(resource: string, side?: 'north' | 'south') {
639
+ return !!this.buyableResources!.find(r => r.resource == resource && r.side == side);
640
+ }
641
+
642
+ get hasBuyableNorth() {
643
+ return !!this.buyableResources?.some(r => r.side === 'north');
644
+ }
645
+
646
+ get hasBuyableSouth() {
647
+ return !!this.buyableResources?.some(r => r.side === 'south');
440
648
  }
441
649
 
442
- buyResource(resource: string) {
443
- this.$emit('buyResource', resource);
650
+ buyResource(resource: string, side?: 'north' | 'south') {
651
+ this.$emit('buyResource', { resource, side });
444
652
  }
445
653
  }
446
654
  </script>
@@ -10,7 +10,7 @@ function launchSelfContained(selector = '#app') {
10
10
 
11
11
  const emitter = launch(selector);
12
12
 
13
- let gameState = setup(3, { map: 'Brazil', variant: 'original', showMoney: true, randomizeMap: true }, '3');
13
+ let gameState = setup(6, { map: 'Korea', variant: 'recharged', showMoney: true, randomizeMap: false }, '3');
14
14
 
15
15
  // gameState.map.viewBox = [1480, 1060];
16
16
  // gameState.map.playerOrderPosition = [1160, 140];
@@ -21,6 +21,7 @@ export interface Piece {
21
21
  color?: string;
22
22
  powerPlant?: PowerPlant;
23
23
  transparent?: boolean;
24
+ side?: 'north' | 'south';
24
25
  }
25
26
 
26
27
  export enum PieceType {