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.7.0",
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.11.0"
21
+ "powergrid-engine": "1.13.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/assert": "^1.4.7",
@@ -49,12 +49,13 @@
49
49
 
50
50
  <Map
51
51
  ref="map"
52
- :transform="`translate(${G.map.mapPosition[0]}, ${G.map.mapPosition[1]})`"
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 v-if="!isIndiaResourceMarket" width="760" height="80" x="20" y="40" rx="3" fill="goldenrod" />
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
- // Korea: lay out cubes within 8 price spaces ($1..$8) using the prices array
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 > 8 (uranium corner $10/$12/$14/$16) — caller renders
365
- // those separately.
366
- private buildKoreaMainRowPieces(
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 > 8) return;
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.buildKoreaMainRowPieces(
413
- gameState.coalPrices!, gameState.coalMarket, 'coal', 'south', 48,
454
+ this.coals = this.buildMainRowPieces(
455
+ gameState.coalPrices!, gameState.coalMarket, 'coal', 48, { side: 'south' },
414
456
  );
415
- this.oils = this.buildKoreaMainRowPieces(
416
- gameState.oilPrices!, gameState.oilMarket, 'oil', 'south', 70,
457
+ this.oils = this.buildMainRowPieces(
458
+ gameState.oilPrices!, gameState.oilMarket, 'oil', 70, { side: 'south' },
417
459
  );
418
- this.garbages = this.buildKoreaMainRowPieces(
419
- gameState.garbagePrices!, gameState.garbageMarket, 'garbage', 'south', 94,
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.buildKoreaMainRowPieces(
450
- gameState.coalPricesNorth!, gameState.coalMarketNorth!, 'coal_north', 'north', -92,
491
+ this.coalsNorth = this.buildMainRowPieces(
492
+ gameState.coalPricesNorth!, gameState.coalMarketNorth!, 'coal_north', -92, { side: 'north' },
451
493
  );
452
- this.oilsNorth = this.buildKoreaMainRowPieces(
453
- gameState.oilPricesNorth!, gameState.oilMarketNorth!, 'oil_north', 'north', -70,
494
+ this.oilsNorth = this.buildMainRowPieces(
495
+ gameState.oilPricesNorth!, gameState.oilMarketNorth!, 'oil_north', -70, { side: 'north' },
454
496
  );
455
- this.garbagesNorth = this.buildKoreaMainRowPieces(
456
- gameState.garbagePricesNorth!, gameState.garbageMarketNorth!, 'garbage_north', 'north', -46,
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(r => r.resource == resource && r.side == side);
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>
@@ -10,30 +10,31 @@ function launchSelfContained(selector = '#app') {
10
10
 
11
11
  const emitter = launch(selector);
12
12
 
13
- let gameState = setup(6, { map: 'Korea', variant: 'recharged', showMoney: true, randomizeMap: false }, '3');
14
-
15
- // gameState.map.viewBox = [1480, 1060];
16
- // gameState.map.playerOrderPosition = [1160, 140];
17
- // gameState.map.cityCountPosition = [0, 0];
18
- // gameState.map.powerPlantMarketPosition = [745, 0];
19
- // gameState.map.mapPosition = [0, 100];
20
- // gameState.map.buttonsPosition = [1305, 0];
21
- // gameState.map.playerBoardsPosition = [1105, 200];
22
- // gameState.map.roundInfoPosition = [20, 590];
23
- // gameState.map.supplyPosition = [0, 720];
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.cities = gameState.map.cities.map(city => ({
27
- // ...city,
28
- // x: city.x * gameState.map.adjustRatio![0],
29
- // y: city.y * gameState.map.adjustRatio![1]
30
- // }));
31
-
32
- // gameState.map.cities = gameState.map.cities.map(city => ({
33
- // ...city,
34
- // x: city.y,
35
- // y: 600 - city.x
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}`;
@@ -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 {