Skip to content

Commit 8e3302b

Browse files
authored
Merge pull request #15 from feat/ping-fps
2 parents c2c2e6b + f5ba2df commit 8e3302b

14 files changed

Lines changed: 1601 additions & 1 deletion

File tree

client/css/style.css

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,76 @@ kbd {
547547
font-size: 1.2rem;
548548
}
549549

550+
/* Ping indicator styles */
551+
.ping-indicator {
552+
position: relative;
553+
}
554+
555+
.ping-unit {
556+
font-size: 0.75rem;
557+
opacity: 0.8;
558+
margin-left: 2px;
559+
}
560+
561+
.ping-indicator.ping-good i {
562+
color: #00ff88;
563+
}
564+
565+
.ping-indicator.ping-medium i {
566+
color: #ffaa00;
567+
}
568+
569+
.ping-indicator.ping-bad i {
570+
color: #ff4444;
571+
}
572+
573+
.ping-indicator.ping-good #ping-text {
574+
color: #00ff88;
575+
}
576+
577+
.ping-indicator.ping-medium #ping-text {
578+
color: #ffaa00;
579+
}
580+
581+
.ping-indicator.ping-bad #ping-text {
582+
color: #ff4444;
583+
}
584+
585+
/* FPS indicator styles */
586+
.fps-indicator {
587+
position: relative;
588+
}
589+
590+
.fps-unit {
591+
font-size: 0.75rem;
592+
opacity: 0.8;
593+
margin-left: 2px;
594+
}
595+
596+
.fps-indicator.fps-good i {
597+
color: #00ff88;
598+
}
599+
600+
.fps-indicator.fps-medium i {
601+
color: #ffaa00;
602+
}
603+
604+
.fps-indicator.fps-bad i {
605+
color: #ff4444;
606+
}
607+
608+
.fps-indicator.fps-good #fps-text {
609+
color: #00ff88;
610+
}
611+
612+
.fps-indicator.fps-medium #fps-text {
613+
color: #ffaa00;
614+
}
615+
616+
.fps-indicator.fps-bad #fps-text {
617+
color: #ff4444;
618+
}
619+
550620
#power-up-indicator {
551621
display: flex;
552622
gap: 15px;

client/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ <h3><i class="fas fa-medal"></i> Current Leaderboard</h3>
130130
<i class="fas fa-skull" aria-hidden="true"></i>
131131
<span id="deaths-text" aria-label="Deaths">0</span>
132132
</div>
133+
<div class="stat-item ping-indicator" title="Network Latency">
134+
<i class="fas fa-signal" aria-hidden="true"></i>
135+
<span id="ping-text" aria-label="Ping">0</span>
136+
<span class="ping-unit">ms</span>
137+
</div>
138+
<div class="stat-item fps-indicator" title="Frames Per Second">
139+
<i class="fas fa-tachometer-alt" aria-hidden="true"></i>
140+
<span id="fps-text" aria-label="FPS">0</span>
141+
<span class="fps-unit">fps</span>
142+
</div>
133143
</div>
134144
</div>
135145
<div class="hud-section center">

client/js/game-client.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ class GameClient {
3939
this.playerName = null;
4040
this.serverAddress = null;
4141

42+
// FPS tracking
43+
this.fps = 0;
44+
this.frameCount = 0;
45+
this.lastFpsUpdate = Date.now();
46+
4247
this.init();
4348
}
4449

@@ -86,10 +91,14 @@ class GameClient {
8691

8792
gameHud.setAttribute('aria-hidden', 'false');
8893

94+
// Start ping monitoring
95+
this.networkManager.startPingMonitoring();
96+
8997
this.gameLoop();
9098
}
9199

92100
onGameDisconnected() {
101+
this.networkManager.stopPingMonitoring();
93102
this.uiManager.hideLoadingOverlay();
94103
this.uiManager.showNotification('Connection lost. Refreshing in 3 seconds...', 'error');
95104
setTimeout(() => location.reload(), 3000);
@@ -133,6 +142,9 @@ class GameClient {
133142
case 'game_over':
134143
this.handleGameOver(msg);
135144
break;
145+
case 'pong':
146+
this.networkManager.handlePong(msg.timestamp);
147+
break;
136148
case 'voice-offer':
137149
this.voiceChatManager.handleOffer(msg);
138150
break;
@@ -288,6 +300,18 @@ class GameClient {
288300
this.sendMove();
289301
this.renderer.updateParticles();
290302

303+
// Calculate FPS
304+
this.frameCount++;
305+
const now = Date.now();
306+
const elapsed = now - this.lastFpsUpdate;
307+
308+
// Update FPS every second
309+
if (elapsed >= 1000) {
310+
this.fps = Math.round((this.frameCount * 1000) / elapsed);
311+
this.frameCount = 0;
312+
this.lastFpsUpdate = now;
313+
}
314+
291315
// Decay heat level
292316
this.heatLevel = Math.max(0, this.heatLevel - CONFIG.WEAPON.HEAT_DECAY_RATE);
293317

@@ -316,5 +340,5 @@ class GameClient {
316340

317341
// Start the game
318342
document.addEventListener('DOMContentLoaded', () => {
319-
new GameClient();
343+
window.gameClient = new GameClient();
320344
});

client/js/managers/network-manager.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ export class NetworkManager {
44
this.game = gameClient;
55
this.ws = null;
66
this.lobbyWs = null;
7+
this.pingInterval = null;
8+
this.lastPingTimestamp = null;
9+
this.currentPing = 0;
710
}
811

912
connectToLobby(serverAddress) {
@@ -91,6 +94,39 @@ export class NetworkManager {
9194
}
9295
}
9396

97+
startPingMonitoring() {
98+
console.log('[PING] Starting ping monitoring...');
99+
// Send ping every 2 seconds
100+
this.pingInterval = setInterval(() => {
101+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
102+
this.lastPingTimestamp = Date.now();
103+
console.log('[PING] Sending ping at:', this.lastPingTimestamp);
104+
this.sendMessage({ type: 'ping', timestamp: this.lastPingTimestamp.toString() });
105+
}
106+
}, 2000);
107+
}
108+
109+
stopPingMonitoring() {
110+
if (this.pingInterval) {
111+
clearInterval(this.pingInterval);
112+
this.pingInterval = null;
113+
}
114+
}
115+
116+
handlePong(timestamp) {
117+
if (timestamp && this.lastPingTimestamp) {
118+
const sentTime = parseInt(timestamp);
119+
if (sentTime === this.lastPingTimestamp) {
120+
this.currentPing = Date.now() - sentTime;
121+
console.log('[PING] Received pong! RTT:', this.currentPing, 'ms');
122+
}
123+
}
124+
}
125+
126+
getPing() {
127+
return this.currentPing;
128+
}
129+
94130
closeLobby() {
95131
if (this.lobbyWs) {
96132
this.lobbyWs.close();

client/js/managers/ui-manager.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ export class UIManager {
118118
const healthText = document.getElementById('health-text');
119119
const killsText = document.getElementById('kills-text');
120120
const deathsText = document.getElementById('deaths-text');
121+
const pingText = document.getElementById('ping-text');
122+
const fpsText = document.getElementById('fps-text');
121123

122124
healthText.textContent = health;
123125
healthText.setAttribute('aria-label', `Health: ${health}`);
@@ -128,6 +130,54 @@ export class UIManager {
128130
deathsText.textContent = deaths;
129131
deathsText.setAttribute('aria-label', `Deaths: ${deaths}`);
130132

133+
// Update ping display
134+
const ping = this.game.networkManager.getPing();
135+
if (pingText) {
136+
// Show "..." if ping is exactly 0 (not yet measured)
137+
if (ping === 0 && this.game.networkManager.lastPingTimestamp !== null) {
138+
pingText.textContent = '...';
139+
} else {
140+
pingText.textContent = ping;
141+
}
142+
pingText.setAttribute('aria-label', `Ping: ${ping} milliseconds`);
143+
144+
// Color code ping based on quality
145+
const pingContainer = pingText.parentElement;
146+
if (pingContainer) {
147+
pingContainer.classList.remove('ping-good', 'ping-medium', 'ping-bad');
148+
if (ping === 0) {
149+
// Neutral color while waiting for first pong
150+
pingContainer.classList.add('ping-medium');
151+
} else if (ping < 50) {
152+
pingContainer.classList.add('ping-good');
153+
} else if (ping < 100) {
154+
pingContainer.classList.add('ping-medium');
155+
} else {
156+
pingContainer.classList.add('ping-bad');
157+
}
158+
}
159+
}
160+
161+
// Update FPS display
162+
const fps = this.game.fps || 0;
163+
if (fpsText) {
164+
fpsText.textContent = fps;
165+
fpsText.setAttribute('aria-label', `FPS: ${fps}`);
166+
167+
// Color code FPS based on performance
168+
const fpsContainer = fpsText.parentElement;
169+
if (fpsContainer) {
170+
fpsContainer.classList.remove('fps-good', 'fps-medium', 'fps-bad');
171+
if (fps >= 50) {
172+
fpsContainer.classList.add('fps-good');
173+
} else if (fps >= 30) {
174+
fpsContainer.classList.add('fps-medium');
175+
} else if (fps > 0) {
176+
fpsContainer.classList.add('fps-bad');
177+
}
178+
}
179+
}
180+
131181
this.updatePowerUpIndicator(myPlayer);
132182
this.updateHeatBar(heatLevel, maxHeatLevel);
133183
this.updateLeaderboard(players, playerId);

docs/dev/PING_DISPLAY_LOCATION.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Ping Display Location
2+
3+
## Screen Layout
4+
5+
```
6+
┌─────────────────────────────────────────────────────────────────────────────┐
7+
│ ┌─ TOP BAR ──────────────────────────────────────────────────────────────┐ │
8+
│ │ ❤️ 100 🎯 0 💀 0 📶 45 ms [POWERUP] [🎤] [⚙️] │ │
9+
│ │ └──┬──┘ └─┬─┘ └─┬─┘ └──┬───┘ │ │
10+
│ │ Health Kills Deaths PING ← NEW! │ │
11+
│ └────────────────────────────────────────────────────────────────────────┘ │
12+
│ │
13+
│ ┌─ LEADERBOARD ──┐ ┌─ KILL FEED ──────┐ │
14+
│ │ 🏆 LEADERBOARD │ │ Player1 💀 You │ │
15+
│ │ #1 Tank123: 5│ └──────────────────┘ │
16+
│ │ #2 You: 3 │ │
17+
│ │ #3 Pro99: 2 │ │
18+
│ └───────────────┘ │
19+
│ │
20+
│ GAME CANVAS AREA │
21+
│ (Tanks, Bullets, PowerUps) │
22+
│ │
23+
│ │
24+
│ ┌─ HEAT BAR ──┐ ┌─ MINIMAP ──┐ ┌─ CHAT ────────────┐ │
25+
│ │ H │ │ │ ┌─────────┐ │ │ 💬 TEAM CHAT │ │
26+
│ │ E │ 100% │ │ │ ● ■ │ │ │ Player: Hi! │ │
27+
│ │ A │█████████│ │ │ ● │ │ │ Tank99: GG │ │
28+
│ │ T │█████████│ │ │ ■ │ │ │ [Type message...] │ │
29+
│ └───┴─────────┘ └─┴─────────┴─┘ └───────────────────┘ │
30+
└─────────────────────────────────────────────────────────────────────────────┘
31+
```
32+
33+
## Ping Indicator Details
34+
35+
### Position
36+
37+
- **Location**: Top-left section of screen
38+
- **Container**: `#top-bar > .hud-section > #player-stats`
39+
- **Order**: 4th item (after Health, Kills, Deaths)
40+
41+
### Visual Appearance
42+
43+
```
44+
┌─────────────┐
45+
│ 📶 45 ms │ ← Green (Good: <50ms)
46+
└─────────────┘
47+
48+
┌─────────────┐
49+
│ 📶 75 ms │ ← Orange (Medium: 50-100ms)
50+
└─────────────┘
51+
52+
┌─────────────┐
53+
│ 📶 150 ms │ ← Red (Bad: >100ms)
54+
└─────────────┘
55+
```
56+
57+
### Color Coding System
58+
59+
| Ping Range | Color | Icon Color | Text Color | Connection Quality |
60+
| ---------- | ------ | ---------- | ---------- | ------------------ |
61+
| 0-49ms | Green | #00ff88 | #00ff88 | Excellent ⚡ |
62+
| 50-99ms | Orange | #ffaa00 | #ffaa00 | Good ✓ |
63+
| 100ms+ | Red | #ff4444 | #ff4444 | Poor ⚠️ |
64+
65+
### Responsive Behavior
66+
67+
#### Desktop (>1250px)
68+
69+
- Full visibility with icon + value + unit
70+
- Font size: 1.1rem
71+
- Padding: 8px 15px
72+
73+
#### Tablet (768px - 1250px)
74+
75+
- Slightly reduced padding: 6px 10px
76+
- Font size: 0.9rem
77+
- Still fully visible
78+
79+
#### Mobile (<768px)
80+
81+
- Condensed padding: 5px 8px
82+
- Font size: 0.8rem
83+
- Icon + value only (unit may wrap)
84+
85+
## Accessibility Features
86+
87+
- ARIA label: "Ping: X milliseconds"
88+
- Tooltip: "Network Latency"
89+
- High contrast colors for all connection states
90+
- Updates every 2 seconds (non-intrusive)
91+
- No animation (reduces motion sickness)
92+
93+
## Technical Integration
94+
95+
The ping display integrates seamlessly with existing UI:
96+
97+
- Uses same styling as other stats (`.stat-item`)
98+
- Updates via `UIManager.updateHUD()`
99+
- Data source: `NetworkManager.getPing()`
100+
- No layout shifts (fixed space allocation)

0 commit comments

Comments
 (0)