powergrid-viewer 2.0.8 → 2.0.10
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/powergrid-viewer.umd.min.js +2 -2
- package/package.json +1 -1
- package/src/components/Game.vue +59 -17
- package/src/components/buttons/ResourceViewButton.vue +68 -0
- package/src/components/buttons/index.js +12 -1
- package/src/launch.ts +1 -0
- package/src/types/ui-data.ts +5 -0
- package/src/util/player-order.ts +44 -0
package/package.json
CHANGED
package/src/components/Game.vue
CHANGED
|
@@ -142,13 +142,21 @@
|
|
|
142
142
|
</template>
|
|
143
143
|
</g>
|
|
144
144
|
|
|
145
|
+
<g v-if="stacked" ref="slotResourceView" :transform="slotT('resourceView')">
|
|
146
|
+
<ResourceViewButton :showTrack="showResourceTrack" @select="setResourceView($event)" />
|
|
147
|
+
</g>
|
|
148
|
+
|
|
145
149
|
<g ref="slotResources" :transform="slotT('resources')">
|
|
146
150
|
<!-- On a phone the printed price track is a strip of unreadable
|
|
147
|
-
columns, so the stacked layout
|
|
148
|
-
source.
|
|
149
|
-
|
|
151
|
+
columns, so the stacked layout defaults to one box per buyable
|
|
152
|
+
source. It is still only a default: the switch above this row
|
|
153
|
+
puts the printed track back, because the boxes deliberately
|
|
154
|
+
throw away the price ladder and that ladder is most of what
|
|
155
|
+
there is to know about uranium. Landscape and desktop keep the
|
|
156
|
+
printed board exactly as it was — an either/or, never an
|
|
157
|
+
overlay. -->
|
|
150
158
|
<ResourceBoxes
|
|
151
|
-
v-if="stacked"
|
|
159
|
+
v-if="stacked && !showResourceTrack"
|
|
152
160
|
:transform="`translate(${G.map.supplyPosition[0]}, ${G.map.supplyPosition[1]})`"
|
|
153
161
|
:gameState="G"
|
|
154
162
|
:player="player"
|
|
@@ -744,6 +752,7 @@ import {
|
|
|
744
752
|
HelpButton,
|
|
745
753
|
RulesButton,
|
|
746
754
|
LayoutButton,
|
|
755
|
+
ResourceViewButton,
|
|
747
756
|
} from './buttons';
|
|
748
757
|
import PlayerBoard from './PlayerBoard.vue';
|
|
749
758
|
import Calculator from './Calculator.vue';
|
|
@@ -757,6 +766,7 @@ import { LogMove } from 'powergrid-engine/src/log';
|
|
|
757
766
|
import { Phase, playerTimeUsed, PowerPlant, PowerPlantType, ResourceType } from 'powergrid-engine/src/gamestate';
|
|
758
767
|
import { City } from 'powergrid-engine/src/maps';
|
|
759
768
|
import { formatDuration } from '../util/time';
|
|
769
|
+
import { playerOrderForDisplay } from '../util/player-order';
|
|
760
770
|
|
|
761
771
|
// Portrait layout: the rows the scene is broken into, top to bottom. Slots on
|
|
762
772
|
// the same row sit side by side and share one scale. Names map to the `slotX`
|
|
@@ -771,6 +781,9 @@ const STACK_ROWS: string[][] = [
|
|
|
771
781
|
// the draw pile and the round readout are things you glance at.
|
|
772
782
|
['buttons', 'roundInfo', 'powerPlantDeck'],
|
|
773
783
|
['powerPlantMarket'],
|
|
784
|
+
// The resource-display switch sits between the two markets rather than in the
|
|
785
|
+
// icon column: it belongs to the row it changes, and reads as that row's header.
|
|
786
|
+
['resourceView'],
|
|
774
787
|
['resources'],
|
|
775
788
|
['uraniumMines'],
|
|
776
789
|
['freeJump'],
|
|
@@ -814,6 +827,9 @@ const STACK_SLOT_MAX_WIDTH: Record<string, number> = {
|
|
|
814
827
|
// The box market is one tall stack of full-width buttons; run edge to edge it
|
|
815
828
|
// reads as though it had been cropped rather than laid out.
|
|
816
829
|
resources: 0.94,
|
|
830
|
+
// Matched to `resources` on purpose: equal budgets render equal widths, so the
|
|
831
|
+
// switch and the market below it line up instead of nearly lining up.
|
|
832
|
+
resourceView: 0.94,
|
|
817
833
|
};
|
|
818
834
|
|
|
819
835
|
/**
|
|
@@ -883,6 +899,7 @@ const round = (n: number, digits = 2) => Number(n.toFixed(digits));
|
|
|
883
899
|
HelpButton,
|
|
884
900
|
RulesButton,
|
|
885
901
|
LayoutButton,
|
|
902
|
+
ResourceViewButton,
|
|
886
903
|
Button,
|
|
887
904
|
Calculator,
|
|
888
905
|
PowerPlantMarket,
|
|
@@ -2121,6 +2138,43 @@ export default class Game extends Vue {
|
|
|
2121
2138
|
this.relayout();
|
|
2122
2139
|
}
|
|
2123
2140
|
|
|
2141
|
+
/**
|
|
2142
|
+
* Only ever consulted inside the stacked layout — landscape and desktop draw the
|
|
2143
|
+
* printed board regardless, so this preference cannot reach them.
|
|
2144
|
+
*/
|
|
2145
|
+
get showResourceTrack(): boolean {
|
|
2146
|
+
return this.preferences.portraitResourceTrack === true;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
setResourceView(showTrack: boolean) {
|
|
2150
|
+
if (showTrack === this.showResourceTrack) {
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
this.emitter.emit('update:preference', { name: 'portraitResourceTrack', value: showTrack });
|
|
2155
|
+
this.preferences.portraitResourceTrack = showTrack;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
/**
|
|
2159
|
+
* The two resource displays are different shapes, so switching between them
|
|
2160
|
+
* invalidates the row heights the last layout solved for — the same trap as
|
|
2161
|
+
* entering the stacked layout at all. Measure again once the swap has landed.
|
|
2162
|
+
*/
|
|
2163
|
+
@Watch('showResourceTrack')
|
|
2164
|
+
onResourceViewChanged() {
|
|
2165
|
+
this.$nextTick(() => {
|
|
2166
|
+
// The printed track fills itself imperatively and `createPieces` only ever
|
|
2167
|
+
// runs on a state update, so a track switched on mid-turn would draw an
|
|
2168
|
+
// EMPTY market until the next move landed — a display that lies. Fill it
|
|
2169
|
+
// before measuring: the cubes are what give the group its size.
|
|
2170
|
+
if (this.G) {
|
|
2171
|
+
this.resources?.createPieces(this.G);
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
this.scheduleRelayout();
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2124
2178
|
relayout() {
|
|
2125
2179
|
if (!this.viewportIsMeasurable()) {
|
|
2126
2180
|
// Still hidden. Whoever reveals us triggers the observer below.
|
|
@@ -2302,19 +2356,7 @@ export default class Game extends Vue {
|
|
|
2302
2356
|
}
|
|
2303
2357
|
|
|
2304
2358
|
get adjustedPlayerOrder() {
|
|
2305
|
-
|
|
2306
|
-
return [];
|
|
2307
|
-
}
|
|
2308
|
-
|
|
2309
|
-
if (!this.preferences.adjustPlayerOrder) {
|
|
2310
|
-
return this.G.players.map((_p, i) => i); // 0 1 2 3 ...
|
|
2311
|
-
}
|
|
2312
|
-
|
|
2313
|
-
if (this.G.phase == Phase.Auction) {
|
|
2314
|
-
return this.G.playerOrder;
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
return this.G.playerOrder.reverse();
|
|
2359
|
+
return playerOrderForDisplay(this.G, this.preferences.adjustPlayerOrder);
|
|
2318
2360
|
}
|
|
2319
2361
|
|
|
2320
2362
|
getResourceResupply() {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!--
|
|
3
|
+
Which resource display the portrait layout shows. A labelled either/or rather
|
|
4
|
+
than an icon: this changes what the whole row below it *is*, and no picture of
|
|
5
|
+
"boxes" or "a track" is recognisable before you tap it.
|
|
6
|
+
|
|
7
|
+
Authored 620x72 so that, held to the same 0.94 width budget as the box market
|
|
8
|
+
underneath, the two line up edge to edge and each half clears the 44px tap
|
|
9
|
+
guideline on a 414px-wide phone.
|
|
10
|
+
-->
|
|
11
|
+
<g class="resourceView">
|
|
12
|
+
<g class="button enabled" @click="$emit('select', false)">
|
|
13
|
+
<rect
|
|
14
|
+
x="0"
|
|
15
|
+
y="0"
|
|
16
|
+
width="306"
|
|
17
|
+
height="72"
|
|
18
|
+
rx="6"
|
|
19
|
+
:fill="showTrack ? 'gainsboro' : '#333'"
|
|
20
|
+
stroke="black"
|
|
21
|
+
stroke-width="3"
|
|
22
|
+
/>
|
|
23
|
+
<text
|
|
24
|
+
x="153"
|
|
25
|
+
y="47"
|
|
26
|
+
text-anchor="middle"
|
|
27
|
+
font-weight="700"
|
|
28
|
+
:fill="showTrack ? 'black' : 'white'"
|
|
29
|
+
style="font-size: 30px"
|
|
30
|
+
>
|
|
31
|
+
Buy boxes
|
|
32
|
+
</text>
|
|
33
|
+
<title>One box per buyable source: the price you pay, and the whole box is the tap target</title>
|
|
34
|
+
</g>
|
|
35
|
+
<g class="button enabled" @click="$emit('select', true)">
|
|
36
|
+
<rect
|
|
37
|
+
x="314"
|
|
38
|
+
y="0"
|
|
39
|
+
width="306"
|
|
40
|
+
height="72"
|
|
41
|
+
rx="6"
|
|
42
|
+
:fill="showTrack ? '#333' : 'gainsboro'"
|
|
43
|
+
stroke="black"
|
|
44
|
+
stroke-width="3"
|
|
45
|
+
/>
|
|
46
|
+
<text
|
|
47
|
+
x="467"
|
|
48
|
+
y="47"
|
|
49
|
+
text-anchor="middle"
|
|
50
|
+
font-weight="700"
|
|
51
|
+
:fill="showTrack ? 'white' : 'black'"
|
|
52
|
+
style="font-size: 30px"
|
|
53
|
+
>
|
|
54
|
+
Price track
|
|
55
|
+
</text>
|
|
56
|
+
<title>The printed market board, so the whole price track is visible at once</title>
|
|
57
|
+
</g>
|
|
58
|
+
</g>
|
|
59
|
+
</template>
|
|
60
|
+
<script lang="ts">
|
|
61
|
+
import { Vue, Component, Prop } from 'vue-property-decorator';
|
|
62
|
+
|
|
63
|
+
@Component
|
|
64
|
+
export default class ResourceViewButton extends Vue {
|
|
65
|
+
@Prop()
|
|
66
|
+
showTrack?: boolean;
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
@@ -4,7 +4,18 @@ import LayoutButton from './LayoutButton.vue';
|
|
|
4
4
|
import LogButton from './LogButton.vue';
|
|
5
5
|
import PassButton from './PassButton.vue';
|
|
6
6
|
import SoundButton from './SoundButton.vue';
|
|
7
|
+
import ResourceViewButton from './ResourceViewButton.vue';
|
|
7
8
|
import RulesButton from './RulesButton.vue';
|
|
8
9
|
import UndoButton from './UndoButton.vue';
|
|
9
10
|
|
|
10
|
-
export {
|
|
11
|
+
export {
|
|
12
|
+
Button,
|
|
13
|
+
HelpButton,
|
|
14
|
+
LayoutButton,
|
|
15
|
+
LogButton,
|
|
16
|
+
PassButton,
|
|
17
|
+
ResourceViewButton,
|
|
18
|
+
SoundButton,
|
|
19
|
+
RulesButton,
|
|
20
|
+
UndoButton,
|
|
21
|
+
};
|
package/src/launch.ts
CHANGED
package/src/types/ui-data.ts
CHANGED
|
@@ -13,6 +13,11 @@ export type Preferences = {
|
|
|
13
13
|
// Portrait phones stack the board into full-width rows. Per player, not per
|
|
14
14
|
// game: it describes the screen you are holding, not the rules being played.
|
|
15
15
|
stackOnPortrait: boolean;
|
|
16
|
+
// Within the stacked layout, show the printed price track instead of the box
|
|
17
|
+
// market. The boxes are the buying surface; the track is the reading surface --
|
|
18
|
+
// it is the only place the whole price ladder is visible at once, which matters
|
|
19
|
+
// most for uranium, where the cubes are few and the prices far apart.
|
|
20
|
+
portraitResourceTrack: boolean;
|
|
16
21
|
};
|
|
17
22
|
|
|
18
23
|
export interface Piece {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// `Phase` comes from the package root (the built `dist`), never from
|
|
2
|
+
// `powergrid-engine/src/gamestate`: webpack 4 parses the engine SOURCE for the
|
|
3
|
+
// unit-test bundle and dies on its `??`. Game.vue can import the source because the
|
|
4
|
+
// app build does not; a file with a spec beside it cannot.
|
|
5
|
+
import type { GameState } from 'powergrid-engine';
|
|
6
|
+
import { Phase } from 'powergrid-engine';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The order the player boards are stacked in (`Game.vue`'s `adjustedPlayerOrder`).
|
|
10
|
+
*
|
|
11
|
+
* Two different orders sit on this board and they are easy to confuse -- the issue
|
|
12
|
+
* that produced this file (#141) was exactly that confusion:
|
|
13
|
+
*
|
|
14
|
+
* - TABLE order: where a player sits. Fixed for the whole game. This is what the
|
|
15
|
+
* boards show when the preference is off, and it is why bidding "out of turn
|
|
16
|
+
* order" is usually not a bug at all.
|
|
17
|
+
* - TURN order (`G.playerOrder`): recomputed every round from cities and largest
|
|
18
|
+
* plant. The auction runs down it; resources and building run back UP it.
|
|
19
|
+
*
|
|
20
|
+
* Kept out of the component so it can be tested against a real engine state, the
|
|
21
|
+
* same way `util/resource-rows.ts` is -- and because the component version could not
|
|
22
|
+
* be tested at all, which is how it stayed wrong.
|
|
23
|
+
*
|
|
24
|
+
* Written without optional chaining: webpack 4 parses the unit-test bundle and
|
|
25
|
+
* rejects `?.` (same note as `util/turn-buffer.ts`).
|
|
26
|
+
*/
|
|
27
|
+
export function playerOrderForDisplay(G: GameState | null | undefined, adjustPlayerOrder: boolean): number[] {
|
|
28
|
+
if (!G) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!adjustPlayerOrder) {
|
|
33
|
+
return G.players.map((_p, i) => i); // 0 1 2 3 ...
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Copy before reversing. `Array.reverse` works in place, so reading the order off
|
|
37
|
+
// the state used to rewrite it -- see `player-order.spec.ts`. The engine already
|
|
38
|
+
// spreads before it reverses, in both places it walks the order backwards.
|
|
39
|
+
if (G.phase == Phase.Auction) {
|
|
40
|
+
return [...G.playerOrder];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return [...G.playerOrder].reverse();
|
|
44
|
+
}
|