Compare commits
No commits in common. "4550b1d66a1ea4e02b2d594ebc317f88007d02e0" and "4a07c41f7ce29c39a3c5a2a8164fbfbe0b74cd5b" have entirely different histories.
4550b1d66a
...
4a07c41f7c
5 changed files with 96 additions and 197 deletions
|
|
@ -249,15 +249,12 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Game container ─────────────────────────────────────────────────── */
|
/* ── Game container ─────────────────────────────────────────────────── */
|
||||||
/* No width: 100% — let it size to content (the board wrapper, ~832px).
|
|
||||||
This keeps the board pinned at the same horizontal position whether or
|
|
||||||
not the side panel is visible, and aligns the status bar / score panels
|
|
||||||
with the board rather than with the viewport edge. */
|
|
||||||
.game-container {
|
.game-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.6rem;
|
gap: 0.6rem;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Language switcher (in-game) ────────────────────────────────────── */
|
/* ── Language switcher (in-game) ────────────────────────────────────── */
|
||||||
|
|
@ -328,25 +325,17 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Player score panel ─────────────────────────────────────────────── */
|
/* ── Player score panel ─────────────────────────────────────────────── */
|
||||||
/* Horizontal banner: name on the left, score bars expanding to fill the
|
|
||||||
board width — no more empty right half on large screens. */
|
|
||||||
.player-score-panel {
|
.player-score-panel {
|
||||||
background: var(--ui-parchment);
|
background: var(--ui-parchment);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 0.45rem 1.25rem;
|
padding: 0.45rem 1rem;
|
||||||
font-size: 0.88rem;
|
font-size: 0.88rem;
|
||||||
box-shadow: 0 2px 6px rgba(0,0,0,0.25);
|
box-shadow: 0 2px 6px rgba(0,0,0,0.25);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: 2px solid var(--ui-gold-dark);
|
border-top: 2px solid var(--ui-gold-dark);
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-score-header {
|
.player-score-header { margin-bottom: 0.35rem; }
|
||||||
flex-shrink: 0;
|
|
||||||
min-width: 90px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-name {
|
.player-name {
|
||||||
font-family: var(--font-display);
|
font-family: var(--font-display);
|
||||||
|
|
@ -356,28 +345,25 @@ body {
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bars sit side-by-side (points | holes) filling remaining width */
|
.score-bars { display: flex; flex-direction: column; gap: 5px; }
|
||||||
.score-bars { display: flex; flex-direction: row; gap: 1.5rem; flex: 1; align-items: center; }
|
|
||||||
|
|
||||||
.score-bar-row {
|
.score-bar-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
flex: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.score-bar-label {
|
.score-bar-label {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: #665544;
|
color: #665544;
|
||||||
width: 3rem;
|
width: 3.5rem;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Points bar ─────────────────────────────────────────────────────── */
|
/* ── Points bar ─────────────────────────────────────────────────────── */
|
||||||
.score-bar {
|
.score-bar {
|
||||||
flex: 1;
|
width: 140px;
|
||||||
max-width: 220px;
|
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background: rgba(0,0,0,0.1);
|
background: rgba(0,0,0,0.1);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
@ -438,21 +424,18 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Board + side panel ─────────────────────────────────────────────── */
|
/* ── Board + side panel ─────────────────────────────────────────────── */
|
||||||
/* .board-and-panel is sized to the board wrapper only; the side panel is
|
|
||||||
positioned absolutely so it floats to the right without pushing the
|
|
||||||
board and breaking its horizontal alignment. */
|
|
||||||
.board-and-panel {
|
.board-and-panel {
|
||||||
position: relative;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-panel {
|
.side-panel {
|
||||||
position: absolute;
|
|
||||||
left: calc(100% + 1rem);
|
|
||||||
top: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.65rem;
|
gap: 0.65rem;
|
||||||
width: 200px;
|
min-width: 160px;
|
||||||
padding-top: 0.15rem;
|
padding-top: 0.15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -802,15 +785,13 @@ body {
|
||||||
.board-quarter .field.zone-petit:nth-child(even),
|
.board-quarter .field.zone-petit:nth-child(even),
|
||||||
.board-quarter .field.zone-grand:nth-child(even) { --fc: var(--field-ivory); }
|
.board-quarter .field.zone-grand:nth-child(even) { --fc: var(--field-ivory); }
|
||||||
|
|
||||||
/* Opponent's grand-jan — deep slate-blue / silvery-green ivory.
|
/* Jan de retour — cooler: dark teal / silvery-green ivory */
|
||||||
Previously #1e3d32 was nearly identical to the felt (#1d3d28); now using
|
.board-quarter .field.zone-retour:nth-child(odd) { --fc: #1e3d32; }
|
||||||
a clearly distinguishable cool blue that reads well against the green. */
|
.board-quarter .field.zone-retour:nth-child(even) { --fc: #e5eadc; }
|
||||||
.board-quarter .field.zone-opponent:nth-child(odd) { --fc: #1a4f72; }
|
|
||||||
.board-quarter .field.zone-opponent:nth-child(even) { --fc: #e5eadc; }
|
|
||||||
|
|
||||||
/* Jan de retour — warmer: amber-brown / warm amber ivory */
|
/* Dernier jan — warmer: amber-brown / warm amber ivory */
|
||||||
.board-quarter .field.zone-retour:nth-child(odd) { --fc: #6a2810; }
|
.board-quarter .field.zone-dernier:nth-child(odd) { --fc: #6a2810; }
|
||||||
.board-quarter .field.zone-retour:nth-child(even) { --fc: #f2dfa0; }
|
.board-quarter .field.zone-dernier:nth-child(even) { --fc: #f2dfa0; }
|
||||||
|
|
||||||
/* ── Rest corner (§3) — before .clickable so green wins when interactive ── */
|
/* ── Rest corner (§3) — before .clickable so green wins when interactive ── */
|
||||||
.field.corner { --fc: var(--field-corner) !important; }
|
.field.corner { --fc: var(--field-corner) !important; }
|
||||||
|
|
@ -846,29 +827,6 @@ body {
|
||||||
animation: exit-glow 2s ease-in-out infinite;
|
animation: exit-glow 2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── §6c — Jan hover field highlight ────────────────────────────────── */
|
|
||||||
.field.jan-hovered {
|
|
||||||
--fc: rgba(190, 140, 35, 0.8) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── §6e — Hit (battue) ripple ──────────────────────────────────────── */
|
|
||||||
@keyframes hit-ripple {
|
|
||||||
from { transform: translate(-50%, -50%) scale(0.4); opacity: 0.9; }
|
|
||||||
to { transform: translate(-50%, -50%) scale(2.2); opacity: 0; }
|
|
||||||
}
|
|
||||||
.hit-ripple {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid rgba(200, 164, 72, 0.9);
|
|
||||||
pointer-events: none;
|
|
||||||
animation: hit-ripple 0.5s ease-out forwards;
|
|
||||||
}
|
|
||||||
.hit-ripple-top { top: 26px; }
|
|
||||||
.hit-ripple-bot { bottom: 26px; }
|
|
||||||
|
|
||||||
/* ── Interactive states — after .corner to take visual priority ─────── */
|
/* ── Interactive states — after .corner to take visual priority ─────── */
|
||||||
.field.clickable {
|
.field.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -894,7 +852,7 @@ body {
|
||||||
.board-quarter .field.zone-petit:nth-child(odd) .field-num,
|
.board-quarter .field.zone-petit:nth-child(odd) .field-num,
|
||||||
.board-quarter .field.zone-grand:nth-child(odd) .field-num,
|
.board-quarter .field.zone-grand:nth-child(odd) .field-num,
|
||||||
.board-quarter .field.zone-retour:nth-child(odd) .field-num,
|
.board-quarter .field.zone-retour:nth-child(odd) .field-num,
|
||||||
.board-quarter .field.zone-opponent:nth-child(odd) .field-num {
|
.board-quarter .field.zone-dernier:nth-child(odd) .field-num {
|
||||||
color: rgba(240,215,190,0.38);
|
color: rgba(240,215,190,0.38);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ fn field_zone_class(field_num: u8) -> &'static str {
|
||||||
match field_num {
|
match field_num {
|
||||||
1..=6 => "zone-petit",
|
1..=6 => "zone-petit",
|
||||||
7..=12 => "zone-grand",
|
7..=12 => "zone-grand",
|
||||||
13..=18 => "zone-opponent",
|
13..=18 => "zone-retour",
|
||||||
19..=24 => "zone-retour",
|
19..=24 => "zone-dernier",
|
||||||
_ => "",
|
_ => "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -143,6 +143,7 @@ fn field_center(f: usize, is_white: bool) -> Option<(f32, f32)> {
|
||||||
Some((x, y))
|
Some((x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// SVG `<g>` element drawing one arrow (shadow + gold) from `fp` to `tp`.
|
/// SVG `<g>` element drawing one arrow (shadow + gold) from `fp` to `tp`.
|
||||||
fn arrow_svg(fp: (f32, f32), tp: (f32, f32)) -> AnyView {
|
fn arrow_svg(fp: (f32, f32), tp: (f32, f32)) -> AnyView {
|
||||||
let (x1, y1) = fp;
|
let (x1, y1) = fp;
|
||||||
|
|
@ -262,9 +263,6 @@ pub fn Board(
|
||||||
/// Checker moves to animate on mount (None when board unchanged).
|
/// Checker moves to animate on mount (None when board unchanged).
|
||||||
#[prop(default = None)]
|
#[prop(default = None)]
|
||||||
last_moves: Option<(CheckerMove, CheckerMove)>,
|
last_moves: Option<(CheckerMove, CheckerMove)>,
|
||||||
/// Fields where a hit (battue) was scored this turn — show ripple animation.
|
|
||||||
#[prop(default = vec![])]
|
|
||||||
hit_fields: Vec<u8>,
|
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let board = view_state.board;
|
let board = view_state.board;
|
||||||
let is_move_stage = view_state.active_mp_player == Some(player_id)
|
let is_move_stage = view_state.active_mp_player == Some(player_id)
|
||||||
|
|
@ -320,8 +318,6 @@ pub fn Board(
|
||||||
(dx.abs() >= 1.0 || dy.abs() >= 1.0).then_some((dx, dy))
|
(dx.abs() >= 1.0 || dy.abs() >= 1.0).then_some((dx, dy))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
// §6e — ripple on hit fields (battue).
|
|
||||||
let is_hit_field = hit_fields.contains(&field_num);
|
|
||||||
view! {
|
view! {
|
||||||
<div
|
<div
|
||||||
id={format!("field-{field_num}")}
|
id={format!("field-{field_num}")}
|
||||||
|
|
@ -376,21 +372,6 @@ pub fn Board(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// §6c: highlight fields touched by the hovered jan
|
|
||||||
if let Some(hm) = hovered_moves {
|
|
||||||
let pairs = hm.get();
|
|
||||||
let f = field_num as usize;
|
|
||||||
let highlighted = pairs.iter().any(|(m1, m2)| {
|
|
||||||
(m1.get_from() != 0 && m1.get_from() == f)
|
|
||||||
|| (m1.get_to() != 0 && m1.get_to() == f)
|
|
||||||
|| (m2.get_from() != 0 && m2.get_from() == f)
|
|
||||||
|| (m2.get_to() != 0 && m2.get_to() == f)
|
|
||||||
});
|
|
||||||
if highlighted {
|
|
||||||
cls.push_str(" jan-hovered");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cls
|
cls
|
||||||
}
|
}
|
||||||
on:click=move |_| {
|
on:click=move |_| {
|
||||||
|
|
@ -442,14 +423,7 @@ pub fn Board(
|
||||||
let moves = staged_moves.get();
|
let moves = staged_moves.get();
|
||||||
let val = displayed_value(board, &moves, is_white, field_num);
|
let val = displayed_value(board, &moves, is_white, field_num);
|
||||||
let count = val.unsigned_abs();
|
let count = val.unsigned_abs();
|
||||||
// §6e — ripple on hit (battue) fields; must be inside the
|
(count > 0).then(|| {
|
||||||
// reactive closure so Leptos uses the same direct rendering
|
|
||||||
// path as .arriving (avoids node-move that resets animation).
|
|
||||||
let ripple = is_hit_field.then(|| {
|
|
||||||
let cls = if is_top_row { "hit-ripple hit-ripple-top" } else { "hit-ripple hit-ripple-bot" };
|
|
||||||
view! { <div class=cls></div> }.into_any()
|
|
||||||
});
|
|
||||||
let stack = (count > 0).then(|| {
|
|
||||||
let color = if val > 0 { "white" } else { "black" };
|
let color = if val > 0 { "white" } else { "black" };
|
||||||
let display_n = (count as usize).min(4);
|
let display_n = (count as usize).min(4);
|
||||||
// outermost index: last for top rows, first for bottom rows.
|
// outermost index: last for top rows, first for bottom rows.
|
||||||
|
|
@ -474,9 +448,8 @@ pub fn Board(
|
||||||
<div class=format!("checker {color}")>{label}</div>
|
<div class=format!("checker {color}")>{label}</div>
|
||||||
}.into_any()
|
}.into_any()
|
||||||
}).collect();
|
}).collect();
|
||||||
view! { <div class="checker-stack">{chips}</div> }.into_any()
|
view! { <div class="checker-stack">{chips}</div> }
|
||||||
});
|
})
|
||||||
(ripple, stack)
|
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
@ -514,6 +487,7 @@ pub fn Board(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let (tl, tr, bl, br) = if is_white {
|
let (tl, tr, bl, br) = if is_white {
|
||||||
(&TOP_LEFT_W, &TOP_RIGHT_W, &BOT_LEFT_W, &BOT_RIGHT_W)
|
(&TOP_LEFT_W, &TOP_RIGHT_W, &BOT_LEFT_W, &BOT_RIGHT_W)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -522,9 +496,9 @@ pub fn Board(
|
||||||
|
|
||||||
// Zone label pairs (top-left, top-right, bot-left, bot-right) per perspective.
|
// Zone label pairs (top-left, top-right, bot-left, bot-right) per perspective.
|
||||||
let (label_tl, label_tr, label_bl, label_br) = if is_white {
|
let (label_tl, label_tr, label_bl, label_br) = if is_white {
|
||||||
("", "jan de retour", "grand jan", "petit jan")
|
("jan de retour", "dernier jan", "grand jan", "petit jan")
|
||||||
} else {
|
} else {
|
||||||
("petit jan", "grand jan", "jan de retour", "")
|
("petit jan", "grand jan", "dernier jan", "jan de retour")
|
||||||
};
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::collections::VecDeque;
|
||||||
|
|
||||||
use futures::channel::mpsc::UnboundedSender;
|
use futures::channel::mpsc::UnboundedSender;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use trictrac_store::{Board as StoreBoard, CheckerMove, Color, Dice as StoreDice, Jan, MoveRules};
|
use trictrac_store::{Board as StoreBoard, CheckerMove, Color, Dice as StoreDice, MoveRules};
|
||||||
|
|
||||||
use crate::app::{GameUiState, NetCommand, PauseReason};
|
use crate::app::{GameUiState, NetCommand, PauseReason};
|
||||||
use crate::i18n::*;
|
use crate::i18n::*;
|
||||||
|
|
@ -121,39 +121,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
|
||||||
|
|
||||||
let last_moves = state.last_moves;
|
let last_moves = state.last_moves;
|
||||||
|
|
||||||
// §6e — fields where a battue (hit) was scored; ripple animation shown there.
|
|
||||||
let hit_fields: Vec<u8> = {
|
|
||||||
let is_hit_jan = |jan: &Jan| matches!(
|
|
||||||
jan,
|
|
||||||
Jan::TrueHitSmallJan
|
|
||||||
| Jan::TrueHitBigJan
|
|
||||||
| Jan::TrueHitOpponentCorner
|
|
||||||
| Jan::FalseHitSmallJan
|
|
||||||
| Jan::FalseHitBigJan
|
|
||||||
);
|
|
||||||
let mut fields: Vec<u8> = vec![];
|
|
||||||
for event_opt in [&my_scored_event, &opp_scored_event] {
|
|
||||||
if let Some(event) = event_opt {
|
|
||||||
for entry in &event.jans {
|
|
||||||
if is_hit_jan(&entry.jan) {
|
|
||||||
for (m1, m2) in &entry.moves {
|
|
||||||
for m in [m1, m2] {
|
|
||||||
let to = m.get_to() as u8;
|
|
||||||
if to != 0 && !fields.contains(&to) {
|
|
||||||
fields.push(to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !fields.is_empty() {
|
|
||||||
leptos::logging::log!("[6e] hit_fields = {:?}", fields);
|
|
||||||
}
|
|
||||||
fields
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Capture for closures ───────────────────────────────────────────────────
|
// ── Capture for closures ───────────────────────────────────────────────────
|
||||||
let stage = vs.stage.clone();
|
let stage = vs.stage.clone();
|
||||||
let turn_stage = vs.turn_stage.clone();
|
let turn_stage = vs.turn_stage.clone();
|
||||||
|
|
@ -250,7 +217,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
|
||||||
bar_is_move=is_move_stage
|
bar_is_move=is_move_stage
|
||||||
bar_is_double=is_double_dice
|
bar_is_double=is_double_dice
|
||||||
last_moves=last_moves
|
last_moves=last_moves
|
||||||
hit_fields=hit_fields
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
// ── Side panel (scoring panels only) ─────────────────────────
|
// ── Side panel (scoring panels only) ─────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ The board body between triangles becomes visible as the wood/felt surface — th
|
||||||
**Proposals**:
|
**Proposals**:
|
||||||
|
|
||||||
### 2a. Zone labels
|
### 2a. Zone labels
|
||||||
Add thin labels (`"petit jan"`, `"grand jan"`, `"jan de retour"`) beneath the board-row (or as a subtle strip above/below the quarter). These should use the serif font at very small size and low opacity — decorative, not noisy.
|
Add thin labels (`"petit jan"`, `"grand jan"`, `"jan de retour"`, `"dernier jan"`) beneath the board-row (or as a subtle strip above/below the quarter). These should use the serif font at very small size and low opacity — decorative, not noisy.
|
||||||
|
|
||||||
### 2b. Field color shift per zone
|
### 2b. Field color shift per zone
|
||||||
The physical game uses alternating colors within each quarter, but different quarters can use slightly different base hues:
|
The physical game uses alternating colors within each quarter, but different quarters can use slightly different base hues:
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
This table maps the French game terminology to the English terms used in this codebase (primarily the `store` crate). Where a code identifier exists, it is shown in `monospace`.
|
This table maps the French game terminology to the English terms used in this codebase (primarily the `store` crate). Where a code identifier exists, it is shown in `monospace`.
|
||||||
|
|
||||||
| French | English (code) | Notes |
|
| French | English (code) | Notes |
|
||||||
| -------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- |
|
|---|---|---|
|
||||||
| tablier | board | `Board` |
|
| tablier | board | `Board` |
|
||||||
| case / flèche | field | `Field` (1–24, 0 = exit); "flèche" (arrow) and "case" both refer to a field/point |
|
| case / flèche | field | `Field` (1–24, 0 = exit); "flèche" (arrow) and "case" both refer to a field/point |
|
||||||
| demi-case | half-field | A field occupied by exactly one checker |
|
| demi-case | half-field | A field occupied by exactly one checker |
|
||||||
|
|
@ -14,7 +14,8 @@ This table maps the French game terminology to the English terms used in this co
|
||||||
| bande de sortie | exit rail | Same rail, used as an extra field value during exit |
|
| bande de sortie | exit rail | Same rail, used as an extra field value during exit |
|
||||||
| petit jan | small jan | Fields 1–6; `is_field_in_small_jan` |
|
| petit jan | small jan | Fields 1–6; `is_field_in_small_jan` |
|
||||||
| grand jan | big jan | Fields 7–12 (White's side, opponent's near zone) |
|
| grand jan | big jan | Fields 7–12 (White's side, opponent's near zone) |
|
||||||
| jan de retour | return jan | Fields 19–24; same fields as opponent's small jan ; where checkers gather before exiting; `last quarter` |
|
| jan de retour | return jan | Fields 13–18; same fields as opponent's small jan |
|
||||||
|
| dernier jan / jan de retour | last jan / last quarter | Fields 19–24; where checkers gather before exiting; `last quarter` |
|
||||||
| table des petits jans | small jan table | The board half containing both players' small jans (fields 1–12) |
|
| table des petits jans | small jan table | The board half containing both players' small jans (fields 1–12) |
|
||||||
| table des grands jans | big jan table | The board half containing both players' big jans (fields 13–24) |
|
| table des grands jans | big jan table | The board half containing both players' big jans (fields 13–24) |
|
||||||
| plein (d'un jan) | filled (jan) | All 6 fields of a jan hold ≥ 2 checkers |
|
| plein (d'un jan) | filled (jan) | All 6 fields of a jan hold ≥ 2 checkers |
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue