powergrid-viewer 1.11.13 → 1.11.15

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.11.13",
3
+ "version": "1.11.15",
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.15.13"
21
+ "powergrid-engine": "1.15.15"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/assert": "^1.4.7",
@@ -380,31 +380,10 @@
380
380
  <div :style="'background-color: ' + playerColors[player.id]">{{ player.name }}</div>
381
381
  </th>
382
382
  </tr>
383
- <tr
384
- v-for="(cat, i) in [
385
- 'Income',
386
- 'Spending: Cities',
387
- 'Spending: Connections',
388
- 'Spending: Plants',
389
- 'Spending: Resources',
390
- ]"
391
- :key="'FC_' + cat"
392
- >
393
- <td>{{ cat }}</td>
394
- <td v-for="player in sortedPlayers" :key="'FS' + player.id + i">
395
- <div>
396
- {{
397
- i == 0
398
- ? player.totalIncome
399
- : i == 1
400
- ? player.totalSpentCities
401
- : i == 2
402
- ? player.totalSpentConnections
403
- : i == 3
404
- ? player.totalSpentPlants
405
- : player.totalSpentResources
406
- }}
407
- </div>
383
+ <tr v-for="row in spendingRows" :key="'FC_' + row.label">
384
+ <td>{{ row.label }}</td>
385
+ <td v-for="player in sortedPlayers" :key="'FS' + player.id + row.label">
386
+ <div>{{ row.value(player) }}</div>
408
387
  </td>
409
388
  </tr>
410
389
  </table>
@@ -562,8 +541,9 @@ import CityCount from './boards/CityCount.vue';
562
541
  import Map from './boards/Map.vue';
563
542
  import Resources from './boards/Resources.vue';
564
543
  import { LogMove } from 'powergrid-engine/src/log';
565
- import { Phase, PowerPlant, PowerPlantType, ResourceType } from 'powergrid-engine/src/gamestate';
544
+ import { Phase, playerTimeUsed, PowerPlant, PowerPlantType, ResourceType } from 'powergrid-engine/src/gamestate';
566
545
  import { City } from 'powergrid-engine/src/maps';
546
+ import { formatDuration } from '../util/time';
567
547
 
568
548
  @Component({
569
549
  created(this: Game) {
@@ -1054,7 +1034,10 @@ export default class Game extends Vue {
1054
1034
 
1055
1035
  sendMove(move) {
1056
1036
  if (!this.paused) {
1057
- this.emitter.emit('move', move);
1037
+ // Stamp the move so the engine can advance the per-player clocks. The
1038
+ // engine never reads the system clock itself — the timestamp lives in the
1039
+ // log so replaying a game reproduces the same times.
1040
+ this.emitter.emit('move', { ...move, time: Date.now() });
1058
1041
  }
1059
1042
  }
1060
1043
 
@@ -1405,6 +1388,26 @@ export default class Game extends Vue {
1405
1388
  return playersSortedByScore(this.G!);
1406
1389
  }
1407
1390
 
1391
+ // Rows of the end-of-game Spending table, in display order.
1392
+ get spendingRows(): { label: string; value: (player: Player) => string | number }[] {
1393
+ return [
1394
+ { label: 'Income', value: (p) => p.totalIncome },
1395
+ { label: 'Spending: Cities', value: (p) => p.totalSpentCities },
1396
+ { label: 'Spending: Connections', value: (p) => p.totalSpentConnections },
1397
+ {
1398
+ // What each city on the board effectively cost: the house price plus
1399
+ // the connection cost paid to reach it, averaged over cities built.
1400
+ label: 'Cost per City',
1401
+ value: (p) =>
1402
+ p.cities.length ? ((p.totalSpentCities + p.totalSpentConnections) / p.cities.length).toFixed(1) : '-',
1403
+ },
1404
+ { label: 'Spending: Plants', value: (p) => p.totalSpentPlants },
1405
+ { label: 'Spending: Resources', value: (p) => p.totalSpentResources },
1406
+ // Every clock is stopped once the game ends, so the banked total is final.
1407
+ { label: 'Time Used', value: (p) => formatDuration(playerTimeUsed(p)) },
1408
+ ];
1409
+ }
1410
+
1408
1411
  get mapTransform() {
1409
1412
  if (!this.G?.map) return '';
1410
1413
  const [mx, my] = this.G.map.mapPosition!;
@@ -96,13 +96,27 @@
96
96
 
97
97
  <Uranium :pieceId="'Uranium' + player.id" :targetState="{ x: 215, y: 76 }" />
98
98
  <text text-anchor="middle" x="250" y="85" fill="black">{{ player.uraniumLeft }}</text>
99
+
100
+ <!-- Time this player has spent on the clock. Ticks live while it is their
101
+ turn; sits in the free space right of the resource counters. -->
102
+ <text
103
+ text-anchor="end"
104
+ x="345"
105
+ y="85"
106
+ fill="black"
107
+ :font-weight="isCurrentPlayer ? 700 : 400"
108
+ style="font-size: 13px"
109
+ >
110
+ {{ timeUsed }}
111
+ </text>
99
112
  </g>
100
113
  </template>
101
114
  <script lang="ts">
102
115
  import { MoveName, Player } from 'powergrid-engine';
103
- import { Phase, PowerPlant, PowerPlantType, ResourceType } from 'powergrid-engine/src/gamestate';
116
+ import { Phase, playerTimeUsed, PowerPlant, PowerPlantType, ResourceType } from 'powergrid-engine/src/gamestate';
104
117
  import { Vue, Component, Prop, Inject } from 'vue-property-decorator';
105
118
  import { Preferences } from '../types/ui-data';
119
+ import { formatDuration } from '../util/time';
106
120
  import { Coal, Oil, Garbage, Uranium, Card } from './pieces';
107
121
 
108
122
  @Component({
@@ -133,6 +147,27 @@ export default class PlayerBoard extends Vue {
133
147
 
134
148
  powerPlantClicked?: PowerPlant;
135
149
 
150
+ // Ticks the live clock. Kept in this component rather than in Game so the
151
+ // per-second update only re-renders the player boards, not the whole board SVG.
152
+ now = Date.now();
153
+ private clockTimer?: number;
154
+
155
+ mounted() {
156
+ this.clockTimer = window.setInterval(() => (this.now = Date.now()), 1000);
157
+ }
158
+
159
+ beforeDestroy() {
160
+ if (this.clockTimer != undefined) {
161
+ window.clearInterval(this.clockTimer);
162
+ }
163
+ }
164
+
165
+ // Total time this player has been on the clock, counting the stretch currently
166
+ // running so the active player's timer visibly ticks up.
167
+ get timeUsed(): string {
168
+ return formatDuration(playerTimeUsed(this.player, this.now));
169
+ }
170
+
136
171
  // Horizontal position of the i-th power-plant card. Spacing compresses as the
137
172
  // hand grows so Australia's mines (which don't count toward the 3-plant limit,
138
173
  // so a player can hold many cards) stay on the board instead of overflowing
@@ -0,0 +1,25 @@
1
+ // Formats a duration in milliseconds compactly, at a precision that suits the
2
+ // magnitude. Games range from a few minutes per player to correspondence games
3
+ // running for days, so the unit pair shifts with the size:
4
+ // 45s | 12m 34s | 3h 22m | 5d 3h
5
+ export function formatDuration(ms: number): string {
6
+ if (!ms || ms < 0) {
7
+ return '0s';
8
+ }
9
+
10
+ const totalSeconds = Math.floor(ms / 1000);
11
+ const totalMinutes = Math.floor(totalSeconds / 60);
12
+ const totalHours = Math.floor(totalMinutes / 60);
13
+ const days = Math.floor(totalHours / 24);
14
+
15
+ if (days > 0) {
16
+ return `${days}d ${totalHours % 24}h`;
17
+ }
18
+ if (totalHours > 0) {
19
+ return `${totalHours}h ${totalMinutes % 60}m`;
20
+ }
21
+ if (totalMinutes > 0) {
22
+ return `${totalMinutes}m ${totalSeconds % 60}s`;
23
+ }
24
+ return `${totalSeconds}s`;
25
+ }