feat(client_web): display checkers stacks
This commit is contained in:
parent
01fa837b84
commit
d3a20eb6b6
2 changed files with 104 additions and 74 deletions
|
|
@ -262,7 +262,7 @@ input[type="text"] {
|
|||
/* ── Fields ─────────────────────────────────────────────────────────── */
|
||||
.field {
|
||||
width: 60px;
|
||||
height: 110px;
|
||||
height: 180px;
|
||||
background: #d4a843;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
|
|
@ -295,25 +295,34 @@ input[type="text"] {
|
|||
.top-row .field-num { bottom: auto; top: 2px; }
|
||||
|
||||
/* ── Checkers ───────────────────────────────────────────────────────── */
|
||||
.checkers {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
.checker-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.checker {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
border: 2px solid rgba(0,0,0,0.3);
|
||||
box-shadow: inset 0 2px 4px rgba(255,255,255,0.3), 0 2px 4px rgba(0,0,0,0.3);
|
||||
box-shadow: inset 0 2px 4px rgba(255,255,255,0.3), 0 1px 3px rgba(0,0,0,0.3);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.checkers.white {
|
||||
.checker + .checker { margin-top: 2px; }
|
||||
|
||||
.checker.white {
|
||||
background: radial-gradient(circle at 35% 35%, #ffffff, #cccccc);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.checkers.black {
|
||||
.checker.black {
|
||||
background: radial-gradient(circle at 35% 35%, #555555, #111111);
|
||||
color: #eee;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ use leptos::prelude::*;
|
|||
use crate::trictrac::types::{SerTurnStage, ViewState};
|
||||
|
||||
/// Field numbers in visual display order (left-to-right for each quarter), white's perspective.
|
||||
const TOP_LEFT_W: [u8; 6] = [13, 14, 15, 16, 17, 18];
|
||||
const TOP_LEFT_W: [u8; 6] = [13, 14, 15, 16, 17, 18];
|
||||
const TOP_RIGHT_W: [u8; 6] = [19, 20, 21, 22, 23, 24];
|
||||
const BOT_LEFT_W: [u8; 6] = [12, 11, 10, 9, 8, 7];
|
||||
const BOT_RIGHT_W: [u8; 6] = [ 6, 5, 4, 3, 2, 1];
|
||||
const BOT_LEFT_W: [u8; 6] = [12, 11, 10, 9, 8, 7];
|
||||
const BOT_RIGHT_W: [u8; 6] = [6, 5, 4, 3, 2, 1];
|
||||
|
||||
/// 180° rotation of white's layout: black's pieces (field 24) appear at the bottom.
|
||||
const TOP_LEFT_B: [u8; 6] = [ 1, 2, 3, 4, 5, 6];
|
||||
const TOP_RIGHT_B: [u8; 6] = [ 7, 8, 9, 10, 11, 12];
|
||||
const BOT_LEFT_B: [u8; 6] = [24, 23, 22, 21, 20, 19];
|
||||
const TOP_LEFT_B: [u8; 6] = [1, 2, 3, 4, 5, 6];
|
||||
const TOP_RIGHT_B: [u8; 6] = [7, 8, 9, 10, 11, 12];
|
||||
const BOT_LEFT_B: [u8; 6] = [24, 23, 22, 21, 20, 19];
|
||||
const BOT_RIGHT_B: [u8; 6] = [18, 17, 16, 15, 14, 13];
|
||||
|
||||
/// Returns the displayed board value for `field_num` after applying `staged_moves`.
|
||||
|
|
@ -25,8 +25,12 @@ fn displayed_value(
|
|||
let mut val = base_board[(field_num - 1) as usize];
|
||||
let delta: i8 = if is_white { 1 } else { -1 };
|
||||
for &(from, to) in staged_moves {
|
||||
if from == field_num { val -= delta; }
|
||||
if to == field_num { val += delta; }
|
||||
if from == field_num {
|
||||
val -= delta;
|
||||
}
|
||||
if to == field_num {
|
||||
val += delta;
|
||||
}
|
||||
}
|
||||
val
|
||||
}
|
||||
|
|
@ -42,66 +46,83 @@ pub fn Board(
|
|||
) -> impl IntoView {
|
||||
let board = view_state.board;
|
||||
let is_move_stage = view_state.active_mp_player == Some(player_id)
|
||||
&& matches!(view_state.turn_stage, SerTurnStage::Move | SerTurnStage::HoldOrGoChoice);
|
||||
&& matches!(
|
||||
view_state.turn_stage,
|
||||
SerTurnStage::Move | SerTurnStage::HoldOrGoChoice
|
||||
);
|
||||
let is_white = player_id == 0;
|
||||
|
||||
let fields_from = |nums: &[u8]| -> Vec<AnyView> {
|
||||
nums.iter().map(|&field_num| {
|
||||
view! {
|
||||
<div
|
||||
class=move || {
|
||||
let moves = staged_moves.get();
|
||||
let val = displayed_value(board, &moves, is_white, field_num);
|
||||
let is_mine = if is_white { val > 0 } else { val < 0 };
|
||||
let can_stage = is_move_stage && moves.len() < 2;
|
||||
let sel = selected_origin.get();
|
||||
let fields_from = |nums: &[u8], is_top_row: bool| -> Vec<AnyView> {
|
||||
nums.iter()
|
||||
.map(|&field_num| {
|
||||
view! {
|
||||
<div
|
||||
class=move || {
|
||||
let moves = staged_moves.get();
|
||||
let val = displayed_value(board, &moves, is_white, field_num);
|
||||
let is_mine = if is_white { val > 0 } else { val < 0 };
|
||||
let can_stage = is_move_stage && moves.len() < 2;
|
||||
let sel = selected_origin.get();
|
||||
|
||||
let mut cls = "field".to_string();
|
||||
if can_stage && (sel.is_some() || is_mine) {
|
||||
cls.push_str(" clickable");
|
||||
}
|
||||
if sel == Some(field_num) { cls.push_str(" selected"); }
|
||||
if can_stage && sel.is_some() && sel != Some(field_num) {
|
||||
cls.push_str(" dest");
|
||||
}
|
||||
cls
|
||||
}
|
||||
on:click=move |_| {
|
||||
if !is_move_stage { return; }
|
||||
if staged_moves.get_untracked().len() >= 2 { return; }
|
||||
|
||||
let moves = staged_moves.get_untracked();
|
||||
let val = displayed_value(board, &moves, is_white, field_num);
|
||||
let is_mine = if is_white { val > 0 } else { val < 0 };
|
||||
|
||||
match selected_origin.get_untracked() {
|
||||
Some(origin) if origin == field_num => {
|
||||
selected_origin.set(None);
|
||||
let mut cls = "field".to_string();
|
||||
if can_stage && (sel.is_some() || is_mine) {
|
||||
cls.push_str(" clickable");
|
||||
}
|
||||
Some(origin) => {
|
||||
staged_moves.update(|v| v.push((origin, field_num)));
|
||||
selected_origin.set(None);
|
||||
if sel == Some(field_num) { cls.push_str(" selected"); }
|
||||
if can_stage && sel.is_some() && sel != Some(field_num) {
|
||||
cls.push_str(" dest");
|
||||
}
|
||||
None if is_mine => selected_origin.set(Some(field_num)),
|
||||
None => {}
|
||||
cls
|
||||
}
|
||||
}
|
||||
>
|
||||
<span class="field-num">{field_num}</span>
|
||||
{move || {
|
||||
let moves = staged_moves.get();
|
||||
let val = displayed_value(board, &moves, is_white, field_num);
|
||||
let count = val.unsigned_abs();
|
||||
(count > 0).then(|| {
|
||||
let color = if val > 0 { "white" } else { "black" };
|
||||
view! { <span class=format!("checkers {color}")>{count}</span> }
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
})
|
||||
.collect()
|
||||
on:click=move |_| {
|
||||
if !is_move_stage { return; }
|
||||
if staged_moves.get_untracked().len() >= 2 { return; }
|
||||
|
||||
let moves = staged_moves.get_untracked();
|
||||
let val = displayed_value(board, &moves, is_white, field_num);
|
||||
let is_mine = if is_white { val > 0 } else { val < 0 };
|
||||
|
||||
match selected_origin.get_untracked() {
|
||||
Some(origin) if origin == field_num => {
|
||||
selected_origin.set(None);
|
||||
}
|
||||
Some(origin) => {
|
||||
staged_moves.update(|v| v.push((origin, field_num)));
|
||||
selected_origin.set(None);
|
||||
}
|
||||
None if is_mine => selected_origin.set(Some(field_num)),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
>
|
||||
<span class="field-num">{field_num}</span>
|
||||
{move || {
|
||||
let moves = staged_moves.get();
|
||||
let val = displayed_value(board, &moves, is_white, field_num);
|
||||
let count = val.unsigned_abs();
|
||||
(count > 0).then(|| {
|
||||
let color = if val > 0 { "white" } else { "black" };
|
||||
let display_n = (count as usize).min(4);
|
||||
// outermost index: last for top rows, first for bottom rows.
|
||||
let outer_idx = if is_top_row { display_n - 1 } else { 0 };
|
||||
let chips: Vec<AnyView> = (0..display_n).map(|i| {
|
||||
let label = if i == outer_idx && count >= 5 {
|
||||
count.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
view! {
|
||||
<div class=format!("checker {color}")>{label}</div>
|
||||
}.into_any()
|
||||
}).collect();
|
||||
view! { <div class="checker-stack">{chips}</div> }
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let (tl, tr, bl, br) = if is_white {
|
||||
|
|
@ -113,15 +134,15 @@ pub fn Board(
|
|||
view! {
|
||||
<div class="board">
|
||||
<div class="board-row top-row">
|
||||
<div class="board-quarter">{fields_from(tl)}</div>
|
||||
<div class="board-quarter">{fields_from(tl, true)}</div>
|
||||
<div class="board-bar"></div>
|
||||
<div class="board-quarter">{fields_from(tr)}</div>
|
||||
<div class="board-quarter">{fields_from(tr, true)}</div>
|
||||
</div>
|
||||
<div class="board-center-bar"></div>
|
||||
<div class="board-row bot-row">
|
||||
<div class="board-quarter">{fields_from(bl)}</div>
|
||||
<div class="board-quarter">{fields_from(bl, false)}</div>
|
||||
<div class="board-bar"></div>
|
||||
<div class="board-quarter">{fields_from(br)}</div>
|
||||
<div class="board-quarter">{fields_from(br, false)}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue