Compare commits

...

2 commits

3 changed files with 97 additions and 17 deletions

View file

@ -121,11 +121,15 @@ body {
.portal-card { .portal-card {
background: var(--ui-parchment); background: var(--ui-parchment);
border: 1px solid rgba(200,164,72,0.3); border-radius: 8px;
border-top: 3px solid var(--ui-gold-dark); box-shadow:
border-radius: 6px; 0 20px 60px rgba(0,0,0,0.55),
0 0 3px 3px rgba(42,21,8,0.9)
;
/* box-shadow: 0 4px 16px rgba(0,0,0,0.18); */
/* border: 1px solid rgba(200,164,72,0.3); */
/* border-top: 3px solid var(--ui-gold-dark); */
padding: 1.75rem 2rem; padding: 1.75rem 2rem;
box-shadow: 0 4px 16px rgba(0,0,0,0.18);
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
@ -404,16 +408,22 @@ a:hover { text-decoration: underline; }
/* ── Login card (§11) ───────────────────────────────────────────────── */ /* ── Login card (§11) ───────────────────────────────────────────────── */
.login-card { .login-card {
width: 340px; background: var(--ui-parchment);
margin-top: 5vh;
border-radius: 8px; border-radius: 8px;
overflow: hidden;
box-shadow: box-shadow:
0 20px 60px rgba(0,0,0,0.55), 0 20px 60px rgba(0,0,0,0.55),
0 0 0 1px rgba(200,164,72,0.35), 0 0 3px 3px rgba(42,21,8,0.9)
0 0 0 5px rgba(42,21,8,0.9), ;
0 0 0 6px rgba(200,164,72,0.2); /* box-shadow:
background: var(--ui-parchment); 0 20px 60px rgba(0,0,0,0.55),
0 0 0 1px rgba(200,164,72,0.35),
0 0 0 5px rgba(42,21,8,0.9),
0 0 0 6px rgba(200,164,72,0.2);
*/
/* border-top: 3px solid var(--ui-gold-dark); */
width: 340px;
margin-top: 5vh;
overflow: hidden;
} }
/* Decorative header — row of triangular flèches like the actual board */ /* Decorative header — row of triangular flèches like the actual board */

View file

@ -120,7 +120,7 @@
"link_copied": "Copié !", "link_copied": "Copié !",
"scan_qr": "ou scannez le QR code", "scan_qr": "ou scannez le QR code",
"join_code_label": "Rejoindre par code", "join_code_label": "Rejoindre par code",
"join_code_placeholder": "Code de salle", "join_code_placeholder": "Code de la salle",
"share_btn": "Partager", "share_btn": "Partager",
"nickname_modal_title": "Choisissez votre pseudo", "nickname_modal_title": "Choisissez votre pseudo",
"nickname_modal_hint": "Vous jouerez sous le nom de :", "nickname_modal_hint": "Vous jouerez sous le nom de :",

View file

@ -4,6 +4,7 @@ use crate::dice::Dice;
use crate::game::GameState; use crate::game::GameState;
use crate::player::Color; use crate::player::Color;
use log::info; use log::info;
use rand::seq::IndexedRandom;
use std::cmp; use std::cmp;
use std::collections::HashSet; use std::collections::HashSet;
@ -327,16 +328,45 @@ impl MoveRules {
Ok(()) Ok(())
} }
fn has_checkers_outside_last_quarter(&self) -> bool { // check if there is still a checker left outside the last quarter after the allowed_move
fn has_checkers_outside_last_quarter(&self, allowed_move: Option<CheckerMove>) -> bool {
// Get the unique field allowed outside the last quarter, when the firt move origin is
// outside and the destination is inside the last quarter
let one_allowed = allowed_move
.filter(|m| m.get_to() > 18)
.map(|m| m.get_from());
!self !self
.board .board
.get_color_fields(Color::White) .get_color_fields(Color::White)
.iter() .iter()
.filter(|(field, _count)| *field < 19) .filter(|(field, count)| *field < 19 && !(Some(*field) == one_allowed && *count == 1))
.collect::<Vec<&(usize, i8)>>() .collect::<Vec<&(usize, i8)>>()
.is_empty() .is_empty()
} }
fn forbid_exits(&self) -> bool {
let filtered = self
.board
.get_color_fields(Color::White)
.into_iter()
.filter(|(field, _count)| *field < 19)
.collect::<Vec<(usize, i8)>>();
let max_dice = if self.dice.values.0 > self.dice.values.1 {
self.dice.values.0
} else {
self.dice.values.1
};
match filtered[..] {
// all checkers in the last jan, exits are possible
[] => false,
// if there is only one checker outside the last jan, and it can go to the last jan with
// one of the dice, an exit is possible with the other dice.
[(field, 1)] if field + (max_dice as usize) > 18 => false,
_ => true,
}
}
fn check_exit_rules( fn check_exit_rules(
&self, &self,
moves: &(CheckerMove, CheckerMove), moves: &(CheckerMove, CheckerMove),
@ -345,8 +375,8 @@ impl MoveRules {
if !moves.0.is_exit() && !moves.1.is_exit() { if !moves.0.is_exit() && !moves.1.is_exit() {
return Ok(()); return Ok(());
} }
// toutes les dames doivent être dans le jan de retour // all checkers must be in the return jan
if self.has_checkers_outside_last_quarter() { if self.has_checkers_outside_last_quarter(Some(moves.0)) {
return Err(MoveError::ExitNeedsAllCheckersOnLastQuarter); return Err(MoveError::ExitNeedsAllCheckersOnLastQuarter);
} }
@ -584,7 +614,7 @@ impl MoveRules {
) -> Vec<(CheckerMove, CheckerMove)> { ) -> Vec<(CheckerMove, CheckerMove)> {
let mut moves_seqs = Vec::new(); let mut moves_seqs = Vec::new();
let color = &Color::White; let color = &Color::White;
let forbid_exits = self.has_checkers_outside_last_quarter(); let forbid_exits = self.forbid_exits();
// Precompute non-excedant sequences once so check_exit_rules need not repeat // Precompute non-excedant sequences once so check_exit_rules need not repeat
// the full move generation for every exit-move candidate. // the full move generation for every exit-move candidate.
// Only needed when Exit is not already ignored and exits are actually reachable. // Only needed when Exit is not already ignored and exits are actually reachable.
@ -1687,6 +1717,46 @@ mod tests {
CheckerMove::new(23, 0).unwrap(), CheckerMove::new(23, 0).unwrap(),
); );
assert!(state.check_exit_rules(&moves, None).is_ok()); assert!(state.check_exit_rules(&moves, None).is_ok());
state.dice.values = (2, 6);
state.board.set_positions(
&crate::Color::White,
[
-9, -1, 0, 0, -2, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, -2, 1, 0, 0, 1, 0, 1, 10, 2,
],
);
let moves = (
CheckerMove::new(17, 23).unwrap(),
CheckerMove::new(23, 0).unwrap(),
);
assert!(state.check_exit_rules(&moves, None).is_ok());
state.dice.values = (3, 1);
state.board.set_positions(
&crate::Color::White,
[
-10, -2, 0, 0, 0, 0, -1, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 3, 2, 1,
],
);
let moves = (
CheckerMove::new(22, 0).unwrap(),
CheckerMove::new(24, 0).unwrap(),
);
assert!(state.check_exit_rules(&moves, None).is_ok());
// Bad exit order: the first move must be with the checker furthest from the exit
state.dice.values = (3, 1);
state.board.set_positions(
&crate::Color::White,
[
-10, -2, 0, 0, 0, 0, -1, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 3, 2, 1,
],
);
let moves = (
CheckerMove::new(24, 0).unwrap(),
CheckerMove::new(22, 0).unwrap(),
);
assert!(state.check_exit_rules(&moves, None).is_err());
} }
#[test] #[test]