Skip to content

Commit 016dd0e

Browse files
feat(ui): add floating score animation for incorrect answers
- Display a large red floating score that animates up and fades out over 3 seconds - Shows the amount of points lost when a team answers incorrectly - Includes team information in score-updated event for proper display
1 parent cab7a33 commit 016dd0e

19 files changed

+908
-104
lines changed

app/Console/Commands/BuzzerServer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class BuzzerServer extends Command
1414
*
1515
* @var string
1616
*/
17-
protected $signature = 'app:buzzer-server';
17+
protected $signature = 'buzzer-server';
1818

1919
/**
2020
* The console command description.

app/Events/ClueRevealed.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
namespace App\Events;
44

55
use App\Models\Clue;
6-
use Illuminate\Broadcasting\InteractsWithSockets;
76
use Illuminate\Broadcasting\Channel;
7+
use Illuminate\Broadcasting\InteractsWithSockets;
88
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
99
use Illuminate\Foundation\Events\Dispatchable;
1010
use Illuminate\Queue\SerializesModels;

app/Events/DailyDoubleTriggered.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class DailyDoubleTriggered implements ShouldBroadcastNow
1313
use Dispatchable, InteractsWithSockets, SerializesModels;
1414

1515
public $gameId;
16+
1617
public $clueId;
1718

1819
/**
@@ -46,4 +47,4 @@ public function broadcastWith(): array
4647
'clueId' => $this->clueId,
4748
];
4849
}
49-
}
50+
}

app/Events/GameStateChanged.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace App\Events;
44

5-
use Illuminate\Broadcasting\InteractsWithSockets;
65
use Illuminate\Broadcasting\Channel;
6+
use Illuminate\Broadcasting\InteractsWithSockets;
77
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
88
use Illuminate\Foundation\Events\Dispatchable;
99
use Illuminate\Queue\SerializesModels;

app/Events/ScoreUpdated.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace App\Events;
44

5-
use Illuminate\Broadcasting\InteractsWithSockets;
65
use Illuminate\Broadcasting\Channel;
6+
use Illuminate\Broadcasting\InteractsWithSockets;
77
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
88
use Illuminate\Foundation\Events\Dispatchable;
99
use Illuminate\Queue\SerializesModels;

app/Http/Controllers/Api/BuzzerController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace App\Http\Controllers\Api;
44

5-
use App\Events\BuzzerPressed;
65
use App\Http\Controllers\Controller;
76
use App\Models\Team;
87
use App\Services\BuzzerService;
@@ -38,7 +37,8 @@ public function __invoke(Request $request): JsonResponse
3837
};
3938

4039
try {
41-
broadcast(new BuzzerPressed($team))->toOthers();
40+
// Use centralized buzzer handling logic
41+
$this->buzzerService->handleBuzzerPress($team);
4242

4343
return response()->json([
4444
'success' => true,

app/Livewire/ClueDisplay.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ public function markIncorrect()
130130
$this->dispatch('score-updated',
131131
teamId: $this->buzzerTeam->id,
132132
points: -$pointsDeducted,
133-
correct: false
133+
correct: false,
134+
teamName: $this->buzzerTeam->name,
135+
teamColor: $this->buzzerTeam->color_hex
134136
);
135137
$this->dispatch('clue-answered', clueId: $this->clue->id);
136138
$this->reset(['buzzerTeam', 'showManualTeamSelection', 'wagerAmount']);
@@ -144,7 +146,9 @@ public function markIncorrect()
144146
$this->dispatch('score-updated',
145147
teamId: $this->buzzerTeam->id,
146148
points: -$pointsDeducted,
147-
correct: false
149+
correct: false,
150+
teamName: $this->buzzerTeam->name,
151+
teamColor: $this->buzzerTeam->color_hex
148152
);
149153

150154
// Check if this was the controlling team

app/Livewire/HostControl.php

Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace App\Livewire;
44

5-
use App\Events\BuzzerPressed;
65
use App\Events\ClueRevealed;
76
use App\Events\DailyDoubleTriggered;
87
use App\Events\GameStateChanged;
@@ -28,7 +27,6 @@ class HostControl extends Component
2827
public ?Team $currentTeam = null;
2928

3029
public $teams = [];
31-
3230

3331
// Daily Double
3432
public bool $showDailyDoubleWager = false;
@@ -128,39 +126,12 @@ public function triggerBuzzer($teamId)
128126
return;
129127
}
130128

131-
// Set the team as active (applies to both regular and lightning round)
132-
$this->currentTeam = $team;
133-
$this->game->current_team_id = $teamId;
134-
$this->game->save();
135-
136-
// Check if we're in lightning round
137-
if ($this->game->status === 'lightning_round') {
138-
// For lightning round, dispatch the buzzer event to the lightning round component
139-
broadcast(new GameStateChanged($this->game->id, 'buzzer-pressed', ['teamId' => $teamId]));
140-
141-
// Also broadcast the buzzer sound
142-
broadcast(new BuzzerPressed($team));
129+
// Use centralized buzzer handling logic
130+
$this->buzzerService->handleBuzzerPress($team);
143131

144-
Log::info('Lightning round buzzer triggered manually from host control', [
145-
'game_id' => $this->game->id,
146-
'team_id' => $teamId,
147-
'team_name' => $team->name,
148-
]);
149-
} else {
150-
// Regular game mode
151-
// Broadcast team selection to all clients
152-
broadcast(new GameStateChanged($this->game->id, 'team-selected', ['teamId' => $teamId]));
153-
154-
// Broadcast buzzer event to trigger sound on game board
155-
broadcast(new BuzzerPressed($team));
156-
157-
Log::info('Buzzer triggered manually from host control', [
158-
'game_id' => $this->game->id,
159-
'team_id' => $teamId,
160-
'team_name' => $team->name,
161-
'set_as_active' => true,
162-
]);
163-
}
132+
// Update local state
133+
$this->currentTeam = $team;
134+
$this->game->refresh();
164135
}
165136

166137
// Clue Control
@@ -453,29 +424,29 @@ public function markLightningCorrect()
453424
if ($this->currentTeam) {
454425
// Award points directly here
455426
$scoringService = app(ScoringService::class);
456-
427+
457428
// Need to refresh to get the latest lightning questions
458429
$this->game->refresh();
459430
$currentQuestion = $this->game->lightningQuestions
460431
->where('is_current', true)
461432
->first();
462-
433+
463434
if ($currentQuestion) {
464435
$scoringService->recordLightningAnswer(
465436
$currentQuestion->id,
466437
$this->currentTeam->id,
467438
true
468439
);
469-
440+
470441
// Mark question as answered and not current
471442
$currentQuestion->update([
472443
'is_current' => false,
473444
'is_answered' => true,
474445
]);
475-
446+
476447
// Refresh team to get updated score
477448
$this->currentTeam->refresh();
478-
449+
479450
// Broadcast score update
480451
broadcast(new ScoreUpdated(
481452
$this->game->id,
@@ -484,29 +455,29 @@ public function markLightningCorrect()
484455
200,
485456
true
486457
));
487-
458+
488459
// Get next question
489460
$nextQuestion = $this->game->lightningQuestions
490461
->where('is_answered', false)
491462
->sortBy('order_position')
492463
->first();
493-
464+
494465
if ($nextQuestion) {
495466
$nextQuestion->update(['is_current' => true]);
496467
// Broadcast that we've moved to the next question
497468
broadcast(new GameStateChanged($this->game->id, 'lightning-next-question'));
498469
}
499470
}
500471
}
501-
472+
502473
// Clear current team for next question
503474
$this->currentTeam = null;
504475
$this->game->current_team_id = null;
505476
$this->game->save();
506-
477+
507478
// Now tell the lightning round component to refresh
508479
$this->dispatch('lightning-refresh')->to(LightningRound::class);
509-
480+
510481
// Refresh our own game state
511482
$this->refreshGame();
512483
}
@@ -517,10 +488,10 @@ public function markLightningIncorrect()
517488
// Deduct points from current team
518489
$scoringService = app(ScoringService::class);
519490
$scoringService->deductPoints($this->currentTeam->id, 200);
520-
491+
521492
// Refresh team to get updated score
522493
$this->currentTeam->refresh();
523-
494+
524495
// Broadcast score update
525496
broadcast(new ScoreUpdated(
526497
$this->game->id,
@@ -529,12 +500,12 @@ public function markLightningIncorrect()
529500
-200,
530501
false
531502
));
532-
503+
533504
// Clear current team to allow others to buzz in
534505
$this->currentTeam = null;
535506
$this->game->current_team_id = null;
536507
$this->game->save();
537-
508+
538509
// Broadcast that buzzers are open again
539510
broadcast(new GameStateChanged($this->game->id, 'buzzers-opened'));
540511
}
@@ -564,7 +535,7 @@ public function handleBuzzerWebhook($teamId)
564535
$this->currentTeam = Team::find($teamId);
565536
}
566537
}
567-
538+
568539
#[On('buzzer-pressed')]
569540
public function handleBuzzerPressed($teamId)
570541
{
@@ -575,17 +546,18 @@ public function handleBuzzerPressed($teamId)
575546
$this->game->save();
576547
}
577548
}
578-
549+
579550
#[On('game-state-changed')]
580551
public function handleGameStateChanged($state, $data = [])
581552
{
582-
// Refresh current team when team is selected in lightning round
583-
if ($state === 'team-selected' && isset($data['teamId']) && $this->game->status === 'lightning_round') {
553+
// Handle team selection events from both manual triggers and buzzer API
554+
if (in_array($state, ['team-selected', 'buzzer-pressed']) && isset($data['teamId'])) {
584555
$this->currentTeam = Team::find($data['teamId']);
585-
$this->game->refresh();
556+
$this->game->current_team_id = $data['teamId'];
557+
$this->refreshGame();
586558
}
587559
}
588-
560+
589561
#[On('score-updated')]
590562
public function handleScoreUpdated()
591563
{
@@ -598,7 +570,7 @@ private function refreshGame()
598570
$this->game->refresh();
599571
$this->categories = $this->game->categories->sortBy('position');
600572
$this->teams = $this->game->teams()->get();
601-
573+
602574
// Refresh current team if it exists
603575
if ($this->currentTeam) {
604576
$this->currentTeam = $this->teams->find($this->currentTeam->id);

app/Livewire/Leaderboard.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace App\Livewire;
4+
5+
use App\Models\Game;
6+
use App\Models\Team;
7+
use Livewire\Component;
8+
9+
class Leaderboard extends Component
10+
{
11+
public $teams;
12+
13+
public $game;
14+
15+
public $showAnimation = true;
16+
17+
public function mount($gameId)
18+
{
19+
$this->game = Game::findOrFail($gameId);
20+
21+
$teams = Team::where('game_id', $this->game->id)
22+
->orderByDesc('score')
23+
->get()
24+
->map(function ($team, $index) {
25+
$team->position = $index + 1;
26+
27+
return $team;
28+
});
29+
30+
// Calculate animation delays for bottom-to-top reveal
31+
// Last place gets shortest delay, first place gets longest
32+
$totalTeams = $teams->count();
33+
$this->teams = $teams->map(function ($team) use ($totalTeams) {
34+
$team->animation_delay = ($totalTeams - $team->position) * 0.8;
35+
36+
return $team;
37+
});
38+
}
39+
40+
public function render()
41+
{
42+
return view('livewire.leaderboard')
43+
->layout('layouts.game');
44+
}
45+
}

app/Livewire/LightningRound.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public function handleNextQuestion()
111111
{
112112
$this->nextQuestion();
113113
}
114-
114+
115115
#[On('lightning-refresh')]
116116
public function handleRefresh()
117117
{
@@ -190,9 +190,14 @@ public function nextQuestion()
190190
$this->dispatch('reset-buzzers');
191191
} else {
192192
// Lightning round complete
193-
$this->dispatch('lightning-round-complete');
194193
$this->game->update(['status' => 'finished', 'current_team_id' => null]);
195194
$this->game->refresh();
195+
196+
// Dispatch to browser for redirect - this will trigger the JavaScript listener
197+
$this->dispatch('lightning-round-complete');
198+
199+
// Also broadcast to all clients
200+
broadcast(new \App\Events\GameStateChanged($this->game->id, 'lightning-round-complete', []));
196201
}
197202
}
198203

0 commit comments

Comments
 (0)