feat(poker): rework the guide ranking table into tier columns
Rankings flow down the left column then the right with muted chevrons in the gaps marking the order, the text and example cards sit in their own centered columns, and each row wears its ladder tier. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
1b0e4ed074
commit
8634c17ff2
1 changed files with 60 additions and 32 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { BookOpen } from "lucide-react";
|
import { BookOpen, ChevronDown } from "lucide-react";
|
||||||
import type { Card, Rank, Suit } from "@/lib/minigames/poker";
|
import type { Card, Rank, Suit } from "@/lib/minigames/poker";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
|
@ -17,7 +18,8 @@ interface PokerRanking {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly example: readonly Card[];
|
readonly example: readonly Card[];
|
||||||
readonly description: string;
|
readonly description: string;
|
||||||
readonly color?: string;
|
/** 0 (no styling) through 9 (royal prismatic); see handRankStyle.ts. */
|
||||||
|
readonly tier: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function card(rank: Rank, suit: Suit): Card {
|
function card(rank: Rank, suit: Suit): Card {
|
||||||
|
|
@ -29,57 +31,61 @@ const RANKINGS: readonly PokerRanking[] = [
|
||||||
name: "High card",
|
name: "High card",
|
||||||
example: [card(14, "s"), card(13, "h"), card(9, "d"), card(5, "c"), card(2, "s")],
|
example: [card(14, "s"), card(13, "h"), card(9, "d"), card(5, "c"), card(2, "s")],
|
||||||
description: "No matching ranks, the highest card wins.",
|
description: "No matching ranks, the highest card wins.",
|
||||||
|
tier: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "One pair",
|
name: "One pair",
|
||||||
example: [card(9, "s"), card(9, "h"), card(13, "d"), card(7, "c"), card(3, "s")],
|
example: [card(9, "s"), card(9, "h"), card(13, "d"), card(7, "c"), card(3, "s")],
|
||||||
description: "Two cards share the same rank.",
|
description: "Two cards share the same rank.",
|
||||||
|
tier: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Two pair",
|
name: "Two pair",
|
||||||
example: [card(11, "s"), card(11, "h"), card(4, "d"), card(4, "c"), card(14, "s")],
|
example: [card(11, "s"), card(11, "h"), card(4, "d"), card(4, "c"), card(14, "s")],
|
||||||
description: "Two different pairs plus a fifth card.",
|
description: "Two different pairs plus a fifth card.",
|
||||||
|
tier: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Three of a kind",
|
name: "Three of a kind",
|
||||||
example: [card(7, "s"), card(7, "h"), card(7, "d"), card(13, "c"), card(2, "s")],
|
example: [card(7, "s"), card(7, "h"), card(7, "d"), card(13, "c"), card(2, "s")],
|
||||||
description: "Three cards share the same rank.",
|
description: "Three cards share the same rank.",
|
||||||
|
tier: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Straight",
|
name: "Straight",
|
||||||
example: [card(5, "s"), card(6, "h"), card(7, "d"), card(8, "c"), card(9, "s")],
|
example: [card(5, "s"), card(6, "h"), card(7, "d"), card(8, "c"), card(9, "s")],
|
||||||
description: "Five cards run in rank order.",
|
description: "Five cards run in rank order.",
|
||||||
color: "#ffff00",
|
tier: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Flush",
|
name: "Flush",
|
||||||
example: [card(14, "h"), card(10, "h"), card(7, "h"), card(4, "h"), card(2, "h")],
|
example: [card(14, "h"), card(10, "h"), card(7, "h"), card(4, "h"), card(2, "h")],
|
||||||
description: "Five cards share the same suit.",
|
description: "Five cards share the same suit.",
|
||||||
color: "#ffff00",
|
tier: 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Full house",
|
name: "Full house",
|
||||||
example: [card(13, "s"), card(13, "h"), card(13, "d"), card(8, "c"), card(8, "s")],
|
example: [card(13, "s"), card(13, "h"), card(13, "d"), card(8, "c"), card(8, "s")],
|
||||||
description: "Three of one rank and two of another.",
|
description: "Three of one rank and two of another.",
|
||||||
color: "#ffff00",
|
tier: 6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Four of a kind",
|
name: "Four of a kind",
|
||||||
example: [card(3, "s"), card(3, "h"), card(3, "d"), card(3, "c"), card(14, "s")],
|
example: [card(3, "s"), card(3, "h"), card(3, "d"), card(3, "c"), card(14, "s")],
|
||||||
description: "Four cards share the same rank.",
|
description: "Four cards share the same rank.",
|
||||||
color: "#ffff00",
|
tier: 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Straight flush",
|
name: "Straight flush",
|
||||||
example: [card(5, "s"), card(6, "s"), card(7, "s"), card(8, "s"), card(9, "s")],
|
example: [card(5, "s"), card(6, "s"), card(7, "s"), card(8, "s"), card(9, "s")],
|
||||||
description: "Five cards run in order and share a suit.",
|
description: "Five cards run in order and share a suit.",
|
||||||
color: "#ffff00",
|
tier: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Royal flush",
|
name: "Royal flush",
|
||||||
example: [card(10, "h"), card(11, "h"), card(12, "h"), card(13, "h"), card(14, "h")],
|
example: [card(10, "h"), card(11, "h"), card(12, "h"), card(13, "h"), card(14, "h")],
|
||||||
description: "The ace-high straight flush, the strongest hand.",
|
description: "The ace-high straight flush, the strongest hand.",
|
||||||
color: "#ffff00",
|
tier: 9,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -102,32 +108,54 @@ export function PokerGuide() {
|
||||||
|
|
||||||
<section className="flex flex-col gap-3">
|
<section className="flex flex-col gap-3">
|
||||||
<h2 className="text-sm font-semibold uppercase tracking-wider">Hand rankings</h2>
|
<h2 className="text-sm font-semibold uppercase tracking-wider">Hand rankings</h2>
|
||||||
<ol className="grid gap-3 sm:grid-cols-2">
|
<ol className="grid gap-2 sm:grid-flow-col sm:grid-cols-2 sm:grid-rows-[auto_1rem_auto_1rem_auto_1rem_auto_1rem_auto]">
|
||||||
{RANKINGS.map((ranking, index) => (
|
{RANKINGS.flatMap((ranking, index) => {
|
||||||
<li key={ranking.name} className="flex items-center gap-3 border border-border p-3" style={ranking.color ? ({
|
const isLast = index === RANKINGS.length - 1;
|
||||||
borderColor: ranking.color
|
const columnBreakAfter = index === 4;
|
||||||
}) : {}}>
|
const nodes = [
|
||||||
<span className="w-5 shrink-0 font-mono text-xs text-muted-foreground">
|
<li
|
||||||
{index + 1}
|
key={ranking.name}
|
||||||
</span>
|
className={`relative flex items-center gap-3 border border-border p-3${
|
||||||
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
ranking.tier > 0 ? ` poker-rank-tier-${ranking.tier}` : ""
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
}`}
|
||||||
|
>
|
||||||
|
<span className="w-5 shrink-0 font-mono text-xs text-muted-foreground">
|
||||||
|
{index + 1}
|
||||||
|
</span>
|
||||||
|
<div className="flex min-w-0 flex-1 flex-col justify-center gap-0">
|
||||||
<span className="font-medium">{ranking.name}</span>
|
<span className="font-medium">{ranking.name}</span>
|
||||||
<div className="flex gap-0.5" aria-label={`${ranking.name} example`}>
|
<p className="text-xs leading-tight text-muted-foreground">
|
||||||
{ranking.example.map((exampleCard, cardIndex) => (
|
{ranking.description}
|
||||||
<CardView
|
</p>
|
||||||
key={`${exampleCard.rank}-${exampleCard.suit}`}
|
|
||||||
card={exampleCard}
|
|
||||||
size="mini"
|
|
||||||
dealIndex={cardIndex}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground max-w-1/2">{ranking.description}</p>
|
<div className="flex shrink-0 gap-0.5" aria-label={`${ranking.name} example`}>
|
||||||
</div>
|
{ranking.example.map((exampleCard, cardIndex) => (
|
||||||
</li>
|
<CardView
|
||||||
))}
|
key={`${exampleCard.rank}-${exampleCard.suit}`}
|
||||||
|
card={exampleCard}
|
||||||
|
size="mini"
|
||||||
|
dealIndex={cardIndex}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</li>,
|
||||||
|
];
|
||||||
|
if (!isLast) {
|
||||||
|
nodes.push(
|
||||||
|
<div
|
||||||
|
key={`${ranking.name}-arrow`}
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-center -my-3",
|
||||||
|
columnBreakAfter && "sm:hidden",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ChevronDown className="size-3.5 text-muted-foreground/70" />
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
})}
|
||||||
</ol>
|
</ol>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue