fix(poker): dev panel card pickers ignored the first selection
CardPicker propagated null into onChange when no card was selected yet, so the board picker's parent coerced every first pick to 2 of spades and the hole-card pickers could never hold a value, keeping Queue hand disabled forever. Picks now build a complete card, preserving any already-chosen half, with regression tests on the extracted helper. Co-authored-by: Sisyphus <sisyphus@ohmyopencode.dev>
This commit is contained in:
parent
074fdb02bf
commit
dce6f8f2ee
2 changed files with 37 additions and 13 deletions
|
|
@ -58,6 +58,24 @@ function cardLabel(card: Card): string {
|
||||||
return `${rank} ${suit}`;
|
return `${rank} ${suit}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a complete card from one picker interaction: an empty selection
|
||||||
|
* starts from the 2-of-spades baseline for the not-yet-chosen half, and any
|
||||||
|
* already-chosen half is preserved. Returning a full card (never null) is
|
||||||
|
* what lets a single pick land; the previous null-propagating version made
|
||||||
|
* first picks no-ops and hole-card queuing impossible.
|
||||||
|
*/
|
||||||
|
export function applyCardPick(
|
||||||
|
value: Card | null,
|
||||||
|
part: "rank" | "suit",
|
||||||
|
next: string,
|
||||||
|
): Card {
|
||||||
|
if (part === "rank") {
|
||||||
|
return { rank: Number(next) as Rank, suit: value?.suit ?? "s" };
|
||||||
|
}
|
||||||
|
return { rank: value?.rank ?? 2, suit: next as Suit };
|
||||||
|
}
|
||||||
|
|
||||||
function CardPicker({
|
function CardPicker({
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
|
|
@ -67,13 +85,13 @@ function CardPicker({
|
||||||
readonly value: Card | null;
|
readonly value: Card | null;
|
||||||
readonly onChange: (card: Card | null) => void;
|
readonly onChange: (card: Card | null) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const setRank = (next: string) => onChange(applyCardPick(value, "rank", next));
|
||||||
|
const setSuit = (next: string) => onChange(applyCardPick(value, "suit", next));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="w-10 text-xs text-muted-foreground">{label}</span>
|
<span className="w-10 text-xs text-muted-foreground">{label}</span>
|
||||||
<Select
|
<Select value={value ? String(value.rank) : ""} onValueChange={setRank}>
|
||||||
value={value ? String(value.rank) : ""}
|
|
||||||
onValueChange={(next) => onChange(value ? { ...value, rank: Number(next) as Rank } : null)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 w-20 text-xs">
|
<SelectTrigger className="h-8 w-20 text-xs">
|
||||||
<SelectValue placeholder="Rank" />
|
<SelectValue placeholder="Rank" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -85,10 +103,7 @@ function CardPicker({
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Select
|
<Select value={value?.suit ?? ""} onValueChange={setSuit}>
|
||||||
value={value?.suit ?? ""}
|
|
||||||
onValueChange={(next) => onChange(value ? { ...value, suit: next as Suit } : null)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 w-24 text-xs">
|
<SelectTrigger className="h-8 w-24 text-xs">
|
||||||
<SelectValue placeholder="Suit" />
|
<SelectValue placeholder="Suit" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -191,11 +206,7 @@ export function PokerDevPanel({
|
||||||
<section className="flex flex-col gap-2 border border-border p-3">
|
<section className="flex flex-col gap-2 border border-border p-3">
|
||||||
<h3 className="text-xs font-semibold uppercase tracking-wider">Board queue</h3>
|
<h3 className="text-xs font-semibold uppercase tracking-wider">Board queue</h3>
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<CardPicker
|
<CardPicker label="Card" value={boardDraft} onChange={setBoardDraft} />
|
||||||
label="Card"
|
|
||||||
value={boardDraft}
|
|
||||||
onChange={(card) => setBoardDraft(card ?? { rank: 2, suit: "s" })}
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
type PokerTableState,
|
type PokerTableState,
|
||||||
} from "@/lib/minigames/poker";
|
} from "@/lib/minigames/poker";
|
||||||
import type { Card } from "@/lib/minigames/poker";
|
import type { Card } from "@/lib/minigames/poker";
|
||||||
|
import { applyCardPick } from "@/components/frontend/minigames/poker/PokerDevPanel";
|
||||||
import {
|
import {
|
||||||
adjustStack,
|
adjustStack,
|
||||||
createTable,
|
createTable,
|
||||||
|
|
@ -148,3 +149,15 @@ describe("dev stack adjustment", () => {
|
||||||
expect(() => adjustStack(tableId, 9611, 9, 100)).toThrow();
|
expect(() => adjustStack(tableId, 9611, 9, 100)).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("dev panel card picker", () => {
|
||||||
|
it("builds a complete card from a first rank pick and preserves the chosen suit", () => {
|
||||||
|
expect(applyCardPick(null, "rank", "13")).toEqual(card(13, "s"));
|
||||||
|
expect(applyCardPick(card(13, "h"), "rank", "14")).toEqual(card(14, "h"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds a complete card from a first suit pick and preserves the chosen rank", () => {
|
||||||
|
expect(applyCardPick(null, "suit", "c")).toEqual(card(2, "c"));
|
||||||
|
expect(applyCardPick(card(14, "h"), "suit", "d")).toEqual(card(14, "d"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue