const SHOWCASE = [ { id: "espadas-1", suit: "espadas", rank: 1 }, { id: "bastos-1", suit: "bastos", rank: 1 }, { id: "espadas-7", suit: "espadas", rank: 7 }, { id: "oros-7", suit: "oros", rank: 7 }, ]; function TrucoApp() { const [save, setSave] = useState(null); const [screen, setScreen] = useState("title"); const [match, setMatch] = useState(null); const [helpOpen, setHelpOpen] = useState(false); const [result, setResult] = useState(null); const [lang, setLang] = useState(getLanguage()); useEffect(() => { const fn = (e) => setLang(e.detail.lang); window.addEventListener("languagechange", fn); return () => window.removeEventListener("languagechange", fn); }, []); const recorded = useRef(false); useEffect(() => { const loaded = loadSave(); setSave(loaded); setSoundEnabled(loaded.sound); }, []); useEffect(() => { if (!match) return; if (waitingOn(match) !== "ai") return; const rival = rivalById(match.rivalId); const t = window.setTimeout(() => { const action = chooseAiAction(match, rival.profile); playSfx(action); setMatch((m) => (m ? applyAction(m, action) : m)); }, thinkDelay(rival.profile)); return () => window.clearTimeout(t); }, [match]); useEffect(() => { if (!match || !save) return; if (match.phase !== "matchOver" || !match.matchWinner) return; if (recorded.current) return; recorded.current = true; const rival = rivalById(match.rivalId); const won = match.matchWinner === "player"; const { save: next, delta } = recordMatch(save, { ranked: match.ranked, won, rivalId: rival.id, rivalPower: rival.difficulty, matchId: match.id, }); writeSave(next); setSave(next); setResult({ won, delta, ranked: match.ranked, rival, before: save.lp, after: next.lp, scores: match.scores, }); if (won) sfx.win(); else sfx.lose(); setScreen("result"); }, [match, save]); function persist(partial) { if (!save) return; const next = { ...save, ...partial }; setSave(next); writeSave(next); } function begin(rival, ranked) { unlockAudio(); recorded.current = false; setResult(null); setMatch(startMatch(rival.id, ranked)); setScreen("play"); sfx.deal(); } function dispatch(action) { playSfx(action); setMatch((m) => (m ? applyAction(m, action) : m)); } function playSfx(action) { if (action.type === "PLAY") sfx.play(); else if (action.type === "CALL") sfx.call(); else if (action.type === "RESPOND" && action.response === "quiero") sfx.want(); else if (action.type === "RESPOND" && action.response === "no-quiero") sfx.fold(); else if (action.type === "MAZO") sfx.fold(); else if (action.type === "ACK") sfx.click(); } const rank = lpToRank(save?.lp ?? 0); const rival = match ? rivalById(match.rivalId) : null; return (
{screen === "title" ? ( { unlockAudio(); setScreen("rivals"); }} onRanked={() => { unlockAudio(); setScreen("ranked"); }} onRules={() => setScreen("rules")} onHelp={() => setHelpOpen(true)} onToggleSound={() => { const next = !(save?.sound ?? true); persist({ sound: next }); setSoundEnabled(next); unlockAudio(); }}/>) : null} {screen === "rivals" ? ( setScreen("title")} onPick={(r) => begin(r, false)}/>) : null} {screen === "ranked" && save ? ( setScreen("title")} onQueue={() => begin(rivalForLp(save.lp), true)}/>) : null} {screen === "rules" ? setScreen("title")}/> : null} {screen === "play" && match && rival && save ? ( setHelpOpen(true)} onExit={() => { setMatch(null); setScreen("title"); }} onToggleSound={() => { persist({ sound: !save.sound }); setSoundEnabled(!save.sound); }}/>) : null} {screen === "result" && result && save ? ( { setMatch(null); setScreen("title"); }} onRematch={() => begin(result.rival, result.ranked)} onRanked={() => { setMatch(null); setScreen("ranked"); }}/>) : null} setHelpOpen(false)} match={match}/>
); } function LanguageSelector(){ return ; } function TitleScreen({ rank, sound, onPlay, onRanked, onRules, onHelp, onToggleSound, }) { return (

{t("deck")}

TRUCO URUGUAYO

{t("tagline")}

{t("update")} {t("newTable")}
{SHOWCASE.map((card, i) => (
))}
); } function RivalSelect({ onBack, onPick }) { return (

{t("rivals")}

{t("rivalsDesc")}

{RIVALS.map((r) => ())}
); } function RankedHub({ save, onBack, onQueue, }) { const rank = lpToRank(save.lp); const peak = lpToRank(save.peakLp); const rival = rivalForLp(save.lp); const games = save.rankedWins + save.rankedLosses; const wr = games ? Math.round((save.rankedWins / games) * 100) : 0; return (

{t("ranked")}

{t("rankDesc")}

{t("wins")}
{save.rankedWins}
{t("losses")}
{save.rankedLosses}
WR
{wr}%

{t("peak")}: {rankLabel(peak)} · {t("streak")} {save.streak} ({t("best")} {save.bestStreak})

{t("rankDesc")}

{["madera","hierro","bronce","plata","oro","platino","esmeralda","diamante","maestro","gran_maestro","challenger","retador","retador_dorado"].map((tier)=>{ const rr=lpToRank(TIER_META?.[tier]?.min ?? 0); return
{trTier(tier)}{["madera","hierro","bronce","plata","oro","platino","esmeralda","diamante"].includes(tier)?" IV–I":""}
;})}

{t("nextRival")}

{rival.name}

{rival.difficultyLabel}

{save.history.length ? () : null}
); } function RulesScreen({ onBack }) { return (

{t("rules")}

{RULE_SECTIONS.map((s) => (

{s.title}

{s.body}

))}

{t("hierarchy")}

{POWER_LADDER.slice(0, 4).map((row) => (

{row.label}

))}
); } function ResultScreen({ result, save, onMenu, onRematch, onRanked, }) { const before = lpToRank(result.before); const after = lpToRank(result.after); const promoted = rankLabel(before) !== rankLabel(after) && result.won; return (

{result.ranked ? t("rankedShort") : t("free")}

{result.won ? t("win") : t("lose")}

{result.scores.player} a {result.scores.ai} {t("versus")} {result.rival.name}

“{trRivalField(result.rival.id, "quote", result.rival.quote)}”

{result.ranked ? (

{result.delta >= 0 ? "+" : ""} {result.delta} LP

{promoted ? (

{t("promotion", {rank: rankLabel(after)})}

) : null}

{t("streak")} {save.streak}

) : null}
{result.ranked ? () : null}
); }