Skip to content

Commit ec120c2

Browse files
committed
Fixes #29
1 parent 3a02744 commit ec120c2

3 files changed

Lines changed: 90 additions & 45 deletions

File tree

lucky7-app/frontend/src/app/components/GameScreen.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export interface RulesConfig {
106106
double: boolean;
107107
relance: boolean;
108108
marchandSable: boolean;
109+
legende: boolean;
109110
}
110111

111112
// ---- Rules Dialog ----
@@ -165,6 +166,11 @@ function RulesDialog({ rules, onClose }: { rules: RulesConfig; onClose: () => vo
165166
active={rules.marchandSable}
166167
desc="Score de 3 → immunité ce tour"
167168
/>
169+
<RuleItem
170+
label="Légende"
171+
active={rules.legende}
172+
desc="Score de 11 (5+6) → tous les joueurs avec un dé à 5 ou 6 boivent"
173+
/>
168174
</div>
169175
</div>
170176
</div>
@@ -243,6 +249,9 @@ export default function GameScreen({ players: initialPlayers, rules, onEnd }: Ga
243249
const [result, setResult] = useState<RoundResult | null>(null);
244250
const [luckyProlongations, setLuckyProlongations] = useState(0);
245251
const [looserProlongations, setLooserProlongations] = useState(0);
252+
// Cumulative sip bonus from prolongations (sum of participants−1 per round)
253+
const [luckyProlongBonus, setLuckyProlongBonus] = useState(0);
254+
const [looserProlongBonus, setLooserProlongBonus] = useState(0);
246255

247256
// Prolongation state
248257
const [prolongation, setProlongation] = useState<ProlongationState | null>(null);
@@ -462,7 +471,7 @@ export default function GameScreen({ players: initialPlayers, rules, onEnd }: Ga
462471
? ` (+${luckyProlongations} prolongation${luckyProlongations > 1 ? 's' : ''})`
463472
: '';
464473
msgs.push(
465-
`🏆 ${luckyPlayers[0].name} distribue ${drinks} gorgée${drinks > 1 ? 's' : ''}${ls.score === ann ? ' — score exact !' : ''}${extra}`
474+
`🏆 ${luckyPlayers[0].name} distribue ${drinks} ${drinks > 1 ? 's' : ''}${ls.score === ann ? ' — score exact !' : ''}${extra}`
466475
);
467476
}
468477

@@ -474,7 +483,7 @@ export default function GameScreen({ players: initialPlayers, rules, onEnd }: Ga
474483
? ` (+${looserProlongations} prolongation${looserProlongations > 1 ? 's' : ''})`
475484
: '';
476485
msgs.push(
477-
`💀 ${looserPlayers[0].name} boit ${drinks} gorgée${drinks > 1 ? 's' : ''}${extra}`
486+
`💀 ${looserPlayers[0].name} boit ${drinks} ${drinks > 1 ? 's' : ''}${extra}`
478487
);
479488
}
480489

@@ -486,11 +495,11 @@ export default function GameScreen({ players: initialPlayers, rules, onEnd }: Ga
486495
const diceVal = s.dice1;
487496
if (diceVal === 1) {
488497
msgs.push(
489-
`🎲 ${s.player.name} — Double 1 : distribue 1 gorgée ou fait relancer un joueur`
498+
`🎲 ${s.player.name} — Double 1 : distribue 1 ou fait relancer un joueur`
490499
);
491500
} else {
492501
msgs.push(
493-
`🎲 ${s.player.name} — Double ${diceVal} : distribue ${diceVal} gorgée${diceVal > 1 ? 's' : ''}`
502+
`🎲 ${s.player.name} — Double ${diceVal} : distribue ${diceVal} ${diceVal > 1 ? 's' : ''}`
494503
);
495504
}
496505
});
@@ -505,6 +514,22 @@ export default function GameScreen({ players: initialPlayers, rules, onEnd }: Ga
505514
});
506515
}
507516

517+
// Légende — optionnel
518+
if (rules.legende) {
519+
const legendeTriggered = playerScores.some(s => s.score === 11);
520+
if (legendeTriggered) {
521+
const affected = playerScores.filter(
522+
s => s.dice1 === 5 || s.dice1 === 6 || s.dice2 === 5 || s.dice2 === 6
523+
);
524+
if (affected.length > 0) {
525+
const names = affected.map(s => s.player.name).join(', ');
526+
msgs.push(
527+
`⭐ Légende ! ${names} boi${affected.length > 1 ? 'vent' : 't'} 1 (dé à 5 ou 6)`
528+
);
529+
}
530+
}
531+
}
532+
508533
return msgs;
509534
}
510535

@@ -720,12 +745,12 @@ export default function GameScreen({ players: initialPlayers, rules, onEnd }: Ga
720745
)}
721746
{roll && !showAnim && roll.score === 3 && rules.marchandSable && (
722747
<Badge variant="secondary" className="text-xs shrink-0">
723-
Marchand
748+
Marchand de sable
724749
</Badge>
725750
)}
726751
{relancedPlayerIds.has(player.id) && (
727752
<Badge variant="outline" className="text-xs shrink-0">
728-
Relancé
753+
Relance
729754
</Badge>
730755
)}
731756
</div>
@@ -837,7 +862,7 @@ export default function GameScreen({ players: initialPlayers, rules, onEnd }: Ga
837862
<p className="text-xs text-muted-foreground">
838863
Enjeu :{' '}
839864
<span className="font-semibold text-foreground">
840-
{drinkStake} gorgée{drinkStake > 1 ? 's' : ''}
865+
{drinkStake} {drinkStake > 1 ? 's' : ''}
841866
</span>
842867
{' · '}Annonce : {announcement}
843868
</p>

lucky7-app/frontend/src/app/components/RulesPage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ export default function RulesPage({ onBack }: RulesPageProps) {
8383
<Rule
8484
emoji="💀"
8585
title="Looser"
86-
desc="Le joueur le plus éloigné de l'annonce boit 1 gorgée."
86+
desc="Le joueur le plus éloigné de l'annonce boit 1 gorgée. De manière générale, les gorgées à boire et à distribuer se compensent : un joueur peut choisir de les soustraire ou de les appliquer séparément. Exemple : 4 gorgées à donner + 1 à boire → donner 3 (4 − 1), ou donner 4 et boire 1."
8787
/>
8888
<Rule
8989
emoji="⚖️"
90-
title="Égalité"
91-
desc="En cas d'égalité pour le Lucky ou le Looser, les joueurs concernés rejouent un tour supplémentaire."
90+
title="Égalité & Prolongation"
91+
desc="En cas d'égalité pour le Lucky ou le Looser, les joueurs concernés rejouent. La mise augmente de (nombre de participants − 1) gorgées à chaque prolongation. Exemple : 5 joueurs à égalité → mise passe à 1 + 4 = 5. Si 3 restent encore à égalité → 5 + 2 = 7."
9292
/>
9393
</div>
9494
</Section>

lucky7-app/frontend/src/app/components/SingleDeviceLobby.tsx

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,24 @@ const TOGGLEABLE_RULES: ToggleableRule[] = [
3333
},
3434
{
3535
key: 'relance',
36-
label: 'Relance (Double 1)',
36+
label: 'Relance',
3737
desc: 'Score 1•1 → le joueur peut relancer ses dés une fois',
3838
},
3939
{
4040
key: 'marchandSable',
4141
label: 'Marchand de sable',
42-
desc: 'Score de 3 (1+2) → immunité contre les gorgées ce tour',
42+
desc: 'Score de 3 (1•2) → immunité contre les gorgées ce tour',
43+
},
44+
{
45+
key: 'legende',
46+
label: 'Légende',
47+
desc: 'Score de 11 (5•6) → tous les joueurs avec un dé à 5 ou 6 boivent 1 gorgée',
4348
},
4449
];
4550

4651
const DISABLED_RULES: DisabledRule[] = [
4752
{ label: 'Jeton', desc: 'Score de 7 → boit 1 gorgée' },
4853
{ label: 'Jackpot', desc: 'Trois joueurs à 7 → relance pour distribuer un pot' },
49-
{ label: 'Légende', desc: 'Score de 11 (5+6) → joueurs avec dé à 5 ou 6 boivent' },
5054
{ label: 'Démon', desc: 'Trois joueurs à 6 → relance pour distribuer un pot' },
5155
];
5256

@@ -59,6 +63,7 @@ const DEFAULT_RULES: RulesConfig = {
5963
double: true,
6064
relance: false,
6165
marchandSable: true,
66+
legende: false,
6267
};
6368

6469
// ---- Rules Config Dialog ----
@@ -95,55 +100,70 @@ function RulesConfigDialog({
95100

96101
<div className="flex flex-col gap-5 px-5 py-4">
97102
{/* Règles activables */}
98-
<div className="flex flex-col gap-3">
103+
<div className="flex flex-col gap-2">
99104
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
100105
Règles optionnelles
101106
</p>
102107
{TOGGLEABLE_RULES.map(rule => (
103-
<label key={rule.key} className="flex items-start gap-3 cursor-pointer select-none">
104-
<input
105-
type="checkbox"
106-
checked={rules[rule.key]}
107-
onChange={() => onToggle(rule.key)}
108-
className="mt-0.5 accent-primary shrink-0 w-4 h-4"
109-
/>
110-
<div>
108+
<button
109+
key={rule.key}
110+
type="button"
111+
onClick={() => onToggle(rule.key)}
112+
className={`flex items-center gap-3 w-full text-left rounded-xl px-3 py-3 transition-colors select-none ${
113+
rules[rule.key]
114+
? 'bg-primary/10 ring-1 ring-primary/30'
115+
: 'bg-muted/50 ring-1 ring-transparent'
116+
}`}
117+
>
118+
<div className="flex-1 min-w-0">
111119
<p className="text-sm font-medium leading-tight">{rule.label}</p>
112120
<p className="text-xs text-muted-foreground mt-0.5">{rule.desc}</p>
113121
</div>
114-
</label>
122+
{/* Toggle switch */}
123+
<div
124+
className={`relative shrink-0 w-11 h-6 rounded-full transition-colors duration-200 ${
125+
rules[rule.key] ? 'bg-primary' : 'bg-foreground/20'
126+
}`}
127+
>
128+
<div
129+
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform duration-200 ${
130+
rules[rule.key] ? 'translate-x-5' : 'translate-x-0.5'
131+
}`}
132+
/>
133+
</div>
134+
</button>
115135
))}
116136
</div>
117137

118138
<Separator />
119139

120140
{/* Règles désactivées */}
121-
<div className="flex flex-col gap-3">
141+
<div className="flex flex-col gap-2">
122142
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
123143
Bientôt disponible
124144
</p>
125-
<div className="flex flex-col gap-2">
126-
{DISABLED_RULES.map(rule => (
127-
<label
128-
key={rule.label}
129-
className="flex items-start gap-3 opacity-40 cursor-not-allowed select-none"
130-
>
131-
<input type="checkbox" disabled className="mt-0.5 shrink-0 w-4 h-4" />
132-
<div>
133-
<p className="text-sm font-medium leading-tight">{rule.label}</p>
134-
<p className="text-xs text-muted-foreground mt-0.5">{rule.desc}</p>
135-
</div>
136-
</label>
137-
))}
138-
<label className="flex items-start gap-3 opacity-60 cursor-not-allowed select-none">
139-
<input type="checkbox" disabled className="mt-0.5 shrink-0 w-4 h-4" />
140-
<div>
141-
<p className="text-sm font-medium leading-tight text-destructive">
142-
{HARD_RULE.label}
143-
</p>
144-
<p className="text-xs text-destructive/70 mt-0.5">{HARD_RULE.desc}</p>
145+
{DISABLED_RULES.map(rule => (
146+
<div
147+
key={rule.label}
148+
className="flex items-center gap-3 rounded-xl px-3 py-3 bg-muted/30 opacity-40"
149+
>
150+
<div className="flex-1 min-w-0">
151+
<p className="text-sm font-medium leading-tight">{rule.label}</p>
152+
<p className="text-xs text-muted-foreground mt-0.5">{rule.desc}</p>
153+
</div>
154+
<div className="relative shrink-0 w-11 h-6 rounded-full bg-foreground/20">
155+
<div className="absolute top-0.5 translate-x-0.5 w-5 h-5 bg-white rounded-full shadow" />
145156
</div>
146-
</label>
157+
</div>
158+
))}
159+
<div className="flex items-center gap-3 rounded-xl px-3 py-3 bg-destructive/5 opacity-50">
160+
<div className="flex-1 min-w-0">
161+
<p className="text-sm font-medium leading-tight text-destructive">{HARD_RULE.label}</p>
162+
<p className="text-xs text-destructive/70 mt-0.5">{HARD_RULE.desc}</p>
163+
</div>
164+
<div className="relative shrink-0 w-11 h-6 rounded-full bg-destructive/20">
165+
<div className="absolute top-0.5 translate-x-0.5 w-5 h-5 bg-white rounded-full shadow" />
166+
</div>
147167
</div>
148168
</div>
149169
</div>

0 commit comments

Comments
 (0)