fix(blackjack): show ten-value upcards correctly and auto-refresh the leaderboard
This commit is contained in:
parent
40b7cbb025
commit
c2dc0fd593
2 changed files with 27 additions and 4 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { handValue, type Card, type PublicPlayerView } from "@/lib/minigames/blackjack";
|
import { handValue, type Card, type PublicPlayerView, type Rank } from "@/lib/minigames/blackjack";
|
||||||
import type { BlackjackSnapshot } from "@/app/(frontend)/minigames/blackjack/actions";
|
import type { BlackjackSnapshot } from "@/app/(frontend)/minigames/blackjack/actions";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { CardView } from "./CardView";
|
import { CardView } from "./CardView";
|
||||||
|
|
@ -40,6 +40,15 @@ function signedAmount(value: number): string {
|
||||||
return `${value >= 0 ? "+" : ""}${value.toLocaleString()}`;
|
return `${value >= 0 ? "+" : ""}${value.toLocaleString()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upcard label reads the card's point value, not its rank: face cards (J/Q/K)
|
||||||
|
// count as 10, aces read "ace", and 2-10 show their pip count.
|
||||||
|
function upcardDisplay(rank: Rank | undefined): string {
|
||||||
|
if (!rank) return "";
|
||||||
|
if (rank === 14) return "ace";
|
||||||
|
if (rank >= 11) return "10";
|
||||||
|
return String(rank);
|
||||||
|
}
|
||||||
|
|
||||||
export function BlackjackFeltCanvas({
|
export function BlackjackFeltCanvas({
|
||||||
snapshot,
|
snapshot,
|
||||||
onSnapshot,
|
onSnapshot,
|
||||||
|
|
@ -196,8 +205,7 @@ export function BlackjackFeltCanvas({
|
||||||
)}
|
)}
|
||||||
{view.dealer.holeHidden && view.dealer.cards.length > 0 && (
|
{view.dealer.holeHidden && view.dealer.cards.length > 0 && (
|
||||||
<span className="font-mono text-sm uppercase tracking-[0.2em] text-white/60">
|
<span className="font-mono text-sm uppercase tracking-[0.2em] text-white/60">
|
||||||
Dealer showing{" "}
|
Dealer showing {upcardDisplay(view.dealer.cards[0]?.rank)}
|
||||||
{view.dealer.cards[0]?.rank === 14 ? "ace" : (view.dealer.cards[0]?.rank ?? "")}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Trophy } from "lucide-react";
|
import { Trophy } from "lucide-react";
|
||||||
import type { PresenceParticipant } from "@/hooks/useRealtimePresence";
|
import type { PresenceParticipant } from "@/hooks/useRealtimePresence";
|
||||||
|
import { PROFILE_UPDATE_EVENT } from "@/hooks/useGameTick";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
@ -84,6 +85,20 @@ export function BlackjackLeaderboard({
|
||||||
}: BlackjackLeaderboardProps) {
|
}: BlackjackLeaderboardProps) {
|
||||||
const [rows, setRows] = useState<readonly LeaderboardRow[] | null>(null);
|
const [rows, setRows] = useState<readonly LeaderboardRow[] | null>(null);
|
||||||
const [failed, setFailed] = useState(false);
|
const [failed, setFailed] = useState(false);
|
||||||
|
const [refreshTick, setRefreshTick] = useState(0);
|
||||||
|
|
||||||
|
// Read-only surface: keep it fresh without a page refresh. A profile update
|
||||||
|
// (a fellow player's result) and a 30s poll both bump refreshTick, which
|
||||||
|
// re-runs the fetch below. Rows only update on success, so no loading flash.
|
||||||
|
useEffect(() => {
|
||||||
|
const handleProfileUpdate = () => setRefreshTick((tick) => tick + 1);
|
||||||
|
window.addEventListener(PROFILE_UPDATE_EVENT, handleProfileUpdate);
|
||||||
|
const poll = window.setInterval(() => setRefreshTick((tick) => tick + 1), 30_000);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(PROFILE_UPDATE_EVENT, handleProfileUpdate);
|
||||||
|
window.clearInterval(poll);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
@ -135,7 +150,7 @@ export function BlackjackLeaderboard({
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [currentUserId, refreshKey]);
|
}, [currentUserId, refreshKey, refreshTick]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="rounded-none">
|
<Card className="rounded-none">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue