feat(web client): local heuristic bot
This commit is contained in:
parent
20134ce468
commit
e61448b627
1 changed files with 56 additions and 7 deletions
|
|
@ -1,5 +1,4 @@
|
||||||
use rand::prelude::IndexedRandom;
|
use trictrac_store::{Board, CheckerMove, Color, GameState, MoveRules, Stage, TurnStage};
|
||||||
use trictrac_store::{CheckerMove, Color, GameState, MoveRules, Stage, TurnStage};
|
|
||||||
|
|
||||||
use super::types::{PlayerAction, PreGameRollState};
|
use super::types::{PlayerAction, PreGameRollState};
|
||||||
|
|
||||||
|
|
@ -29,15 +28,65 @@ pub fn bot_decide(game: &GameState, pgr: Option<&PreGameRollState>) -> Option<Pl
|
||||||
TurnStage::Move | TurnStage::HoldOrGoChoice => {
|
TurnStage::Move | TurnStage::HoldOrGoChoice => {
|
||||||
let rules = MoveRules::new(&Color::Black, &game.board, game.dice);
|
let rules = MoveRules::new(&Color::Black, &game.board, game.dice);
|
||||||
let sequences = rules.get_possible_moves_sequences(true, vec![]);
|
let sequences = rules.get_possible_moves_sequences(true, vec![]);
|
||||||
let mut rng = rand::rng();
|
|
||||||
let (m1, m2) = sequences
|
|
||||||
.choose(&mut rng)
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or((CheckerMove::default(), CheckerMove::default()));
|
|
||||||
// MoveRules with Color::Black mirrors the board internally, so
|
// MoveRules with Color::Black mirrors the board internally, so
|
||||||
// returned move coordinates are in mirrored (White) space — mirror back.
|
// returned move coordinates are in mirrored (White) space — mirror back.
|
||||||
|
let (m1, m2) = sequences
|
||||||
|
.iter()
|
||||||
|
.max_by(|(m1a, m2a), (m1b, m2b)| {
|
||||||
|
score_seq(&game.board, m1a, m2a)
|
||||||
|
.partial_cmp(&score_seq(&game.board, m1b, m2b))
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or((CheckerMove::default(), CheckerMove::default()));
|
||||||
Some(PlayerAction::Move(m1.mirror(), m2.mirror()))
|
Some(PlayerAction::Move(m1.mirror(), m2.mirror()))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Score a candidate move sequence from the bot's (Black) perspective.
|
||||||
|
/// `m1` and `m2` are in mirrored (White) space, as returned by MoveRules for Color::Black.
|
||||||
|
fn score_seq(board: &Board, m1: &CheckerMove, m2: &CheckerMove) -> f32 {
|
||||||
|
let mut b = board.mirror();
|
||||||
|
let _ = b.move_checker(&Color::White, *m1);
|
||||||
|
let _ = b.move_checker(&Color::White, *m2);
|
||||||
|
evaluate(&b)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate a board position from White's perspective (call after mirroring for Black).
|
||||||
|
fn evaluate(board: &Board) -> f32 {
|
||||||
|
let mut score = 0.0f32;
|
||||||
|
|
||||||
|
let white_fields = board.get_color_fields(Color::White);
|
||||||
|
let black_fields = board.get_color_fields(Color::Black);
|
||||||
|
|
||||||
|
// Quarter fill progress — quarters 1-6, 7-12, 19-24.
|
||||||
|
// Quarter 13-18 is skipped: field 13 is the opponent's rest corner so White can never fill it.
|
||||||
|
for &q in &[1usize, 7, 19] {
|
||||||
|
if board.is_quarter_filled(Color::White, q) {
|
||||||
|
score += 8.0;
|
||||||
|
} else {
|
||||||
|
let missing = board.get_quarter_filling_candidate(Color::White);
|
||||||
|
score += (6 - missing.len().min(6)) as f32 * 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton exposure: penalise a White singleton at field f only when there is at least
|
||||||
|
// one Black checker at a field g > f (opponent can potentially threaten it).
|
||||||
|
let max_black_field = black_fields.iter().map(|(f, _)| *f).max().unwrap_or(0);
|
||||||
|
for (f, count) in &white_fields {
|
||||||
|
if *count == 1 && *f < max_black_field {
|
||||||
|
score -= 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit zone progress: reward checkers already in fields 19-24.
|
||||||
|
for (field, count) in &white_fields {
|
||||||
|
if *field >= 19 {
|
||||||
|
score += count.abs() as f32 * 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
score
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue