Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/TrucoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ const SIZES = {
sm: "w-12",
md: "w-16",
played: "w-11 sm:w-16",
mini: "w-8 sm:w-12",
hand: "w-[clamp(72px,11vw,120px)]",
pile: "w-[clamp(84px,11vw,132px)]",
} as const;
Expand Down
28 changes: 20 additions & 8 deletions src/components/TrucoTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const TrucoTable: React.FC<Props> = ({ st, version, bubbles, handPoints, notice,
</div>
</header>

<main className="relative flex-1 min-h-0 flex flex-col sm:flex-row items-center justify-center gap-2 p-3 sm:p-6">
<main className="relative flex-1 min-h-0 flex flex-col sm:flex-row items-center justify-center gap-6 sm:gap-2 p-3 sm:p-6">
{/* Tanteador: en el celu arriba de la mesa; en pantallas grandes, en la esquina libre */}
<div className="shrink-0 sm:absolute z-10 sm:top-3 sm:left-3 flex flex-col gap-1.5 px-2.5 py-2 rounded-2xl ring-2 ring-amber-950/80 shadow-[inset_0_1px_0_rgba(255,255,255,.25),0_8px_20px_rgba(0,0,0,.45)]"
style={{ backgroundImage: "repeating-linear-gradient(90deg, transparent 0 38px, rgba(0,0,0,.22) 38px 40px), linear-gradient(#b45309, #451a03)" }}
Expand All @@ -159,7 +159,7 @@ const TrucoTable: React.FC<Props> = ({ st, version, bubbles, handPoints, notice,
<div
key={s}
data-seat={s}
className={`absolute -translate-x-1/2 -translate-y-1/2 flex flex-col items-center gap-1 transition-opacity ${out ? "opacity-40" : ""}`}
className={`absolute z-20 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center gap-1 transition-opacity ${out ? "opacity-40" : ""}`}
style={pos(s, 40, 41)}
>
<div className="relative">
Expand All @@ -176,22 +176,34 @@ const TrucoTable: React.FC<Props> = ({ st, version, bubbles, handPoints, notice,
</span>
<div className="flex -space-x-4">
{partner
? partner.map((c) => <TrucoCard key={c} id={c} size="sm" />)
? partner.map((c) => <TrucoCard key={c} id={c} size="mini" />)
: Array.from({ length: st.counts[s] }).map((_, i) => <TrucoCard key={i} size="xs" />)}
</div>
{Math.sin(angle(s)) <= -0.5 && bubble(s, true)}
</div>
);
})}

{/* Cartas jugadas: cada uno las deja delante suyo, una por baza */}
{/* Cartas jugadas: cada uno las deja delante suyo, una por baza (la j-ésima es de la baza j).
La que ganó la baza brilla, las que perdieron se apagan; en una parda quedan las empatadas */}
{st.order.map((s) => {
const cards = st.played.flatMap((b) => b.filter((p) => p.seat === s).map((p) => p.card));
return (
<div key={s} className="absolute -translate-x-1/2 -translate-y-1/2 flex -space-x-5" style={pos(s, 20, 27)}>
{cards.map((c) => (
<TrucoCard key={c} id={c} size="played" className="animate-card-drop" />
))}
<div key={s} className="absolute -translate-x-1/2 -translate-y-1/2 flex -space-x-5" style={pos(s, 20, 23)}>
{cards.map((c, j) => {
const top = st.bazaTop?.[j];
const won = top && st.results[j] !== "P" && top[0] === s;
const lost = top && !won && !(st.results[j] === "P" && top.includes(s));
return (
<TrucoCard
key={c}
id={c}
size="played"
dimmed={!!lost}
className={`animate-card-drop ${won ? "z-10 -translate-y-1 rounded-lg ring-2 ring-yellow-300 shadow-[0_0_16px_rgba(250,204,21,0.6)]" : ""}`}
/>
);
})}
</div>
);
})}
Expand Down
1 change: 1 addition & 0 deletions src/types/truco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface TrucoState {
counts: number[];
played: { seat: number; card: string }[][];
results: (0 | 1 | 'P')[];
bazaTop: number[][];
envido: {
state: 'NONE' | 'PENDING' | 'DONE';
chain: Llamada[];
Expand Down
5 changes: 5 additions & 0 deletions truco/engine/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,11 @@ function view(m, seat) {
counts: m.cards.map((c) => c.length),
played: h.played.map((b) => b.map((p) => ({ seat: p.seat, card: p.card.id }))),
results: h.results,
// Por baza terminada, quiénes tiraron la carta más alta (en orden de juego): el primero la gana, salvo parda
bazaTop: h.played.slice(0, h.results.length).map((b) => {
const max = Math.max(...b.map((j) => fuerza(j.card)));
return b.filter((j) => fuerza(j.card) === max).map((j) => j.seat);
}),
envido: {
state: h.envido.state, chain: h.envido.chain, caller: h.envido.caller,
// Las cartas del tanto ganador se muestran recién al terminar la mano
Expand Down
10 changes: 10 additions & 0 deletions truco/engine/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,16 @@ test('cada uno ve su mano y la del compañero, nunca la del rival', () => {
assert.deepEqual(T.view(sin, 0).partners, {});
});

test('el 7 de oro le gana al 4 de oro y la vista marca quién ganó cada baza', () => {
const m = partida(2, ['7o 4b 5b', '4o 6c 5c']);
ok(m, 0, 'jugar', { card: '7-oro' });
ok(m, 1, 'jugar', { card: '4-oro' });
const v = T.view(m, 1);
assert.deepEqual(v.results, [0]);
assert.deepEqual(v.bazaTop, [[0]]);
assert.equal(v.turn, 0, 'el que ganó sale en la segunda');
});

test('el que se levanta pierde la partida para su equipo', () => {
const m = partida(4, ['3e 4o 5o', '4b 5b 6b', '3o 4c 5c', '6e 6o 6c']);
assert.deepEqual(T.abandon(m, 2).events.at(-1), { type: 'fin', winner: 1 });
Expand Down
Loading