temporary commit
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["rdraught", "rdraught-cli", "rdraught-pi", "rdraught-ui"]
|
||||
members = ["rdraught", "rdraught-cli", "rdraught-pi", "rdraught-ui", "rdraught-w4"]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
@@ -20,6 +20,8 @@ gtk4 = "0.9"
|
||||
gdk4 = "0.9"
|
||||
gio = "0.20.12"
|
||||
glib = "0.20.12"
|
||||
wasm4 = "0.2.0"
|
||||
wasm4-sys = "0.1.3"
|
||||
|
||||
#[patch.crates-io]
|
||||
#cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core.git", package = "cairo-rs", tag="0.20.12" }
|
||||
|
@@ -63,10 +63,12 @@ fn draw_piece(
|
||||
piece: Piece,
|
||||
crown_red: &SvgHandle,
|
||||
crown_white: &SvgHandle,
|
||||
current_player: Player,
|
||||
) -> Result<(), Error> {
|
||||
if let Piece::NoPiece = piece {
|
||||
Ok(())
|
||||
} else {
|
||||
cr.save()?;
|
||||
let center = square.center();
|
||||
let outer_radius = square.width() * 0.3;
|
||||
let vertical_scale_factor = 0.8;
|
||||
@@ -79,8 +81,7 @@ fn draw_piece(
|
||||
m3.translate(0.0, center.y() - outer_radius * vertical_scale_factor);
|
||||
Matrix::multiply(&Matrix::multiply(&m1, &m2), &m3)
|
||||
};
|
||||
cr.save()?;
|
||||
cr.set_matrix(matrix);
|
||||
cr.set_matrix(Matrix::multiply(&matrix, &cr.matrix()));
|
||||
cr.set_source_rgb(0.0, 0.0, 0.0);
|
||||
let thickness = outer_radius * 0.3;
|
||||
cr.arc(
|
||||
@@ -115,12 +116,16 @@ fn draw_piece(
|
||||
cr.set_source_rgb(color.0, color.1, color.2);
|
||||
cr.arc(
|
||||
center.x(),
|
||||
center.y() - thickness / 2.0,
|
||||
match current_player {
|
||||
Player::White => center.y() + thickness / 2.0,
|
||||
Player::Red => center.y() - thickness / 2.0,
|
||||
},
|
||||
radius,
|
||||
0.0,
|
||||
2.0 * PI,
|
||||
);
|
||||
cr.fill()?;
|
||||
cr.restore()?;
|
||||
if crowned {
|
||||
let renderer = match piece.player() {
|
||||
Some(Player::Red) => rsvg::CairoRenderer::new(crown_red),
|
||||
@@ -129,14 +134,27 @@ fn draw_piece(
|
||||
};
|
||||
let m4 = {
|
||||
let mut m1 = Matrix::identity();
|
||||
m1.translate(-center.x(), -(center.y() - thickness / 1.0));
|
||||
match current_player {
|
||||
Player::Red => m1.translate(-center.x(), -(center.y() - thickness / 1.0)),
|
||||
Player::White => m1.translate(-center.x(), -(center.y() + thickness / 2.0)),
|
||||
}
|
||||
let mut m2 = Matrix::identity();
|
||||
m2.scale(0.5, 0.5);
|
||||
if Player::White == current_player {
|
||||
let m = Matrix::new(1.0, 0.0, 0.0, -1.0, 0.0, 0.0);
|
||||
m2 = Matrix::multiply(&m2, &m);
|
||||
}
|
||||
let mut m3 = Matrix::identity();
|
||||
m3.translate(center.x(), center.y() - thickness / 1.0);
|
||||
match current_player {
|
||||
Player::Red => m3.translate(center.x(), center.y() - thickness / 1.0),
|
||||
Player::White => m3.translate(center.x(), center.y() + thickness / 2.0),
|
||||
}
|
||||
Matrix::multiply(&Matrix::multiply(&m1, &m2), &m3)
|
||||
};
|
||||
cr.set_matrix(Matrix::multiply(&matrix, &m4));
|
||||
cr.set_matrix(Matrix::multiply(
|
||||
&Matrix::multiply(&matrix, &m4),
|
||||
&cr.matrix(),
|
||||
));
|
||||
renderer
|
||||
.render_document(
|
||||
cr,
|
||||
@@ -149,11 +167,54 @@ fn draw_piece(
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
cr.restore()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_score_bar(
|
||||
cr: &CairoContext,
|
||||
board: &Rectangle,
|
||||
draughts_game: &DraughtsGame,
|
||||
xform: &Matrix,
|
||||
) {
|
||||
let score_bar = Rectangle::new(
|
||||
board.tl().x() - board.width() / 10.0,
|
||||
board.tl().y(),
|
||||
board.width() / 16.0,
|
||||
board.height(),
|
||||
);
|
||||
let score_percentage = draughts_game.relative_score(Player::White) as f64;
|
||||
let tl = score_bar.tl();
|
||||
let br = score_bar.br();
|
||||
{
|
||||
let (tlx, tly) = xform.transform_point(tl.x(), tl.y());
|
||||
let (brx, bry) = xform.transform_point(br.x(), br.y());
|
||||
println!(
|
||||
"tl: ({}, {}), br: ({}, {}), score: {}",
|
||||
tlx, tly, brx, bry, score_percentage
|
||||
);
|
||||
}
|
||||
cr.save().unwrap();
|
||||
//cr.set_matrix(*xform);
|
||||
cr.set_source_rgb(1.0, 1.0, 1.0);
|
||||
cr.rectangle(
|
||||
score_bar.tl().x(),
|
||||
score_bar.tl().y(),
|
||||
score_bar.width(),
|
||||
score_bar.height() * score_percentage,
|
||||
);
|
||||
cr.fill().unwrap();
|
||||
cr.set_source_rgb(1.0, 0.0, 0.0);
|
||||
cr.rectangle(
|
||||
tl.x(),
|
||||
tl.y() + score_bar.height() * score_percentage,
|
||||
score_bar.width(),
|
||||
score_bar.height(),
|
||||
);
|
||||
cr.fill().unwrap();
|
||||
cr.restore().unwrap();
|
||||
}
|
||||
|
||||
fn on_activate(application: >k::Application) {
|
||||
// Initialize GTK before using any GTK functions.
|
||||
if gtk::init().is_err() {
|
||||
@@ -202,6 +263,7 @@ fn on_activate(application: >k::Application) {
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
let current_player = Rc::<Player>::new(Player::White);
|
||||
let xform = Rc::<RefCell<Matrix>>::new(RefCell::new(Matrix::identity()));
|
||||
// Set the "draw" function of the drawing area. This callback is called
|
||||
// whenever GTK needs to redraw this widget (for example, on first display or when resized).
|
||||
@@ -209,26 +271,8 @@ fn on_activate(application: >k::Application) {
|
||||
let draughts_game = draughts_game.clone();
|
||||
let xform = xform.clone();
|
||||
let selected_piece = selected_piece.clone();
|
||||
let board_clone = board;
|
||||
let available_moves = available_moves.clone();
|
||||
let get_square_for_position = move |position: &Position, xform: &Matrix| -> Rectangle {
|
||||
let square_size = SQUARE_SIZE;
|
||||
|
||||
let p1 = Point::new(
|
||||
(position.col() as f64) * square_size,
|
||||
((8 - 1 - position.row()) as f64) * square_size,
|
||||
);
|
||||
let p2 = &p1 + &Point::new(square_size, square_size);
|
||||
let square = Rectangle::from_points(&board_clone.tl() + &p1, &board_clone.tl() + &p2);
|
||||
let tl = transform_point(&square.tl(), xform);
|
||||
let br = transform_point(&square.br(), xform);
|
||||
Rectangle::new(
|
||||
f64::min(tl.x(), br.x()),
|
||||
f64::min(tl.y(), br.y()),
|
||||
f64::abs(tl.x() - br.x()),
|
||||
f64::abs(tl.y() - br.y()),
|
||||
)
|
||||
};
|
||||
let current_player = current_player.clone();
|
||||
drawing_area
|
||||
.borrow_mut()
|
||||
.set_draw_func(move |_widget, cr, width, height| {
|
||||
@@ -245,17 +289,30 @@ fn on_activate(application: >k::Application) {
|
||||
let mut xform = xform.borrow_mut();
|
||||
*xform = Matrix::multiply(
|
||||
&Matrix::multiply(
|
||||
&Matrix::new(1.0, 0.0, 0.0, 1.0, -board_center.x(), -board_center.y()),
|
||||
&Matrix::multiply(
|
||||
&Matrix::new(1.0, 0.0, 0.0, 1.0, -board_center.x(), -board_center.y()),
|
||||
&(match *current_player {
|
||||
Player::White => Matrix::new(1.0, 0.0, 0.0, -1.0, 0.0, 0.0),
|
||||
Player::Red => Matrix::new(1.0, 0.0, 0.0, 1.0, 0.0, 0.0),
|
||||
}),
|
||||
),
|
||||
&Matrix::new(f, 0.0, 0.0, f, 0.0, 0.0),
|
||||
),
|
||||
&Matrix::new(1.0, 0.0, 0.0, 1.0, screen_center.x(), screen_center.y()),
|
||||
);
|
||||
cr.set_matrix(*xform);
|
||||
|
||||
// Loop over rows and columns to draw each chessboard cell.
|
||||
for row in 0..DraughtsBoard::rows() {
|
||||
for col in 0..DraughtsBoard::columns() {
|
||||
let position = Position::new((8 - row - 1) as u8, col as u8);
|
||||
let square = get_square_for_position(&position, &xform);
|
||||
let square = Rectangle::new(
|
||||
position.col() as f64 * SQUARE_SIZE,
|
||||
position.row() as f64 * SQUARE_SIZE,
|
||||
SQUARE_SIZE,
|
||||
SQUARE_SIZE,
|
||||
);
|
||||
cr.save().unwrap();
|
||||
// Alternate colors based on the sum of row and column indices.
|
||||
if (row + col) % 2 == 0 {
|
||||
cr.set_source_rgb(0.8, 0.8, 0.6);
|
||||
@@ -277,12 +334,19 @@ fn on_activate(application: >k::Application) {
|
||||
draughts_game.borrow().piece_at(position),
|
||||
&crown_red_handle,
|
||||
&crown_white_handle,
|
||||
*current_player,
|
||||
)
|
||||
.unwrap();
|
||||
cr.restore().unwrap();
|
||||
}
|
||||
}
|
||||
if let Some(selected_postion) = selected_piece.get() {
|
||||
let square = get_square_for_position(&selected_postion, &xform);
|
||||
if let Some(selected_position) = selected_piece.get() {
|
||||
let square = Rectangle::new(
|
||||
selected_position.col() as f64 * SQUARE_SIZE,
|
||||
selected_position.row() as f64 * SQUARE_SIZE,
|
||||
SQUARE_SIZE,
|
||||
SQUARE_SIZE,
|
||||
);
|
||||
cr.save().unwrap();
|
||||
cr.new_path();
|
||||
cr.move_to(square.tl().x(), square.tl().y());
|
||||
@@ -306,7 +370,13 @@ fn on_activate(application: >k::Application) {
|
||||
let am = available_moves.borrow();
|
||||
if !am.is_empty() {
|
||||
for mv in am.iter() {
|
||||
let square = get_square_for_position(&mv.get_end_position(), &xform);
|
||||
let end_pos = mv.get_end_position();
|
||||
let square = Rectangle::new(
|
||||
end_pos.col() as f64 * SQUARE_SIZE,
|
||||
end_pos.row() as f64 * SQUARE_SIZE,
|
||||
SQUARE_SIZE,
|
||||
SQUARE_SIZE,
|
||||
);
|
||||
cr.save().unwrap();
|
||||
cr.new_path();
|
||||
cr.move_to(square.tl().x(), square.tl().y());
|
||||
@@ -327,6 +397,7 @@ fn on_activate(application: >k::Application) {
|
||||
cr.restore().unwrap();
|
||||
}
|
||||
}
|
||||
draw_score_bar(&cr, &board, &draughts_game.borrow(), &xform);
|
||||
});
|
||||
}
|
||||
let gesture = gtk::GestureClick::new();
|
||||
@@ -350,19 +421,21 @@ fn on_activate(application: >k::Application) {
|
||||
if board_clone.contains(&p) {
|
||||
let p = &p - &board_clone.tl();
|
||||
// println!("Point: {:?}", p);
|
||||
let position = Position::new(
|
||||
8 - 1 - (p.y() / SQUARE_SIZE) as u8,
|
||||
(p.x() / SQUARE_SIZE) as u8,
|
||||
);
|
||||
let position =
|
||||
Position::new((p.y() / SQUARE_SIZE) as u8, (p.x() / SQUARE_SIZE) as u8);
|
||||
// println!("Selected position: {:?}", position);
|
||||
let mut draughts_game = draughts_game.borrow_mut();
|
||||
let piece = draughts_game.piece_at(position);
|
||||
// println!("Selected piece: {:?}", piece);
|
||||
let mut am = available_moves.borrow_mut();
|
||||
let mut move_applied = false;
|
||||
if !am.is_empty() {
|
||||
for mv in am.iter() {
|
||||
if mv.get_end_position() == position {
|
||||
draughts_game.apply_move(mv).unwrap();
|
||||
if let Some(mv) = draughts_game.get_best_move(10) {
|
||||
println!("Next best move: {:?}", mv);
|
||||
}
|
||||
move_applied = true;
|
||||
break;
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
use super::board::Board;
|
||||
use super::board::BoardIteratorRef;
|
||||
use super::board::RectangularBoard;
|
||||
use core::iter::{Filter, Map};
|
||||
use core::ops::Index;
|
||||
use core::ops::IndexMut;
|
||||
use core::result;
|
||||
use heapless::BinaryHeap;
|
||||
use heapless::Vec;
|
||||
|
||||
use super::position::Position;
|
||||
@@ -107,15 +107,12 @@ impl DraughtsBoard {
|
||||
|
||||
pub fn pieces<'a, const PIECES: usize>(
|
||||
&'a self,
|
||||
pieces: &'a Vec<Piece, PIECES>,
|
||||
) -> Map<
|
||||
Filter<BoardIteratorRef<'a, Piece, 8, 8>, impl FnMut(&(usize, usize, Piece)) -> bool>,
|
||||
impl FnMut((usize, usize, Piece)) -> Position,
|
||||
> {
|
||||
pieces: Vec<Piece, PIECES>,
|
||||
) -> impl Iterator<Item = Position> {
|
||||
self.0
|
||||
.iter()
|
||||
.filter(move |(_, _, piece)| {
|
||||
for p in pieces {
|
||||
for p in &pieces {
|
||||
if piece == p {
|
||||
return true;
|
||||
}
|
||||
@@ -268,6 +265,9 @@ impl Default for DraughtsGame {
|
||||
}
|
||||
}
|
||||
|
||||
type MoveRanking<const MAX_SIZE: usize> =
|
||||
BinaryHeap<MoveHeapEntry, heapless::binary_heap::Max, MAX_SIZE>;
|
||||
|
||||
impl DraughtsGame {
|
||||
pub fn new() -> DraughtsGame {
|
||||
DraughtsGame::default()
|
||||
@@ -294,7 +294,7 @@ impl DraughtsGame {
|
||||
}
|
||||
}
|
||||
let mut capture_available = false;
|
||||
for pos in self.board.pieces(&needle) {
|
||||
for pos in self.board.pieces(needle) {
|
||||
for _ in self.board.moves_for_piece(pos, true) {
|
||||
capture_available = true;
|
||||
}
|
||||
@@ -371,6 +371,164 @@ impl DraughtsGame {
|
||||
Err(Error::NoPiece)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn score_for_player(&self, p: Player) -> u32 {
|
||||
let md = MoveDirection::NE;
|
||||
let mut score = 0u32;
|
||||
let pieces = {
|
||||
let mut v = Vec::<Piece, 2>::new();
|
||||
match p {
|
||||
Player::White => {
|
||||
v.push(Piece::SimpleWhitePawn).unwrap();
|
||||
v.push(Piece::CrownedWhitePawn).unwrap();
|
||||
v
|
||||
}
|
||||
Player::Red => {
|
||||
v.push(Piece::SimpleRedPawn).unwrap();
|
||||
v.push(Piece::CrownedRedPawn).unwrap();
|
||||
v
|
||||
}
|
||||
}
|
||||
};
|
||||
for pos in self.board.pieces(pieces) {
|
||||
let piece = self.board.get_piece(&pos);
|
||||
if piece.is_crowned() {
|
||||
score += 20;
|
||||
} else {
|
||||
score += 10;
|
||||
if md.is_forward(piece).unwrap() {
|
||||
score += pos.row() as u32;
|
||||
} else {
|
||||
score += DraughtsBoard::rows() as u32 - 1 - pos.row() as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
score
|
||||
}
|
||||
|
||||
pub fn relative_score(&self, p: Player) -> f32 {
|
||||
let red = self.score_for_player(Player::Red);
|
||||
let white = self.score_for_player(Player::White);
|
||||
let subject = match p {
|
||||
Player::White => white,
|
||||
Player::Red => red,
|
||||
};
|
||||
subject as f32 / (red + white) as f32
|
||||
}
|
||||
|
||||
fn move_score(mut self, mv: &Move, player: Player, depth: u32) -> f32 {
|
||||
let pos = mv.get_start_position();
|
||||
let piece = self.board.get_piece(&pos);
|
||||
let moving_player = piece.player().unwrap();
|
||||
self.apply_move(&mv).unwrap();
|
||||
if depth != 0 {
|
||||
let mut best_score = None;
|
||||
for mv in self.available_moves() {
|
||||
let clone = self.clone();
|
||||
let score = clone.move_score(&mv, player, depth - 1);
|
||||
if best_score.is_none() {
|
||||
best_score = Some(score);
|
||||
} else {
|
||||
if let Some(bs) = best_score {
|
||||
if bs < score {
|
||||
best_score = Some(bs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
best_score.unwrap_or_else(|| self.relative_score(player))
|
||||
} else {
|
||||
self.relative_score(player)
|
||||
}
|
||||
}
|
||||
|
||||
fn available_moves<'a>(&'a self) -> impl Iterator<Item = Move> {
|
||||
let mut pieces = Vec::<Piece, 2>::new();
|
||||
match self.next_move {
|
||||
Player::White => {
|
||||
pieces.push(Piece::SimpleWhitePawn).unwrap();
|
||||
pieces.push(Piece::CrownedWhitePawn).unwrap();
|
||||
}
|
||||
Player::Red => {
|
||||
pieces.push(Piece::SimpleRedPawn).unwrap();
|
||||
pieces.push(Piece::CrownedRedPawn).unwrap();
|
||||
}
|
||||
}
|
||||
self.board
|
||||
.pieces(pieces)
|
||||
.flat_map(|pos| self.moves_for_piece(pos))
|
||||
}
|
||||
|
||||
fn find_best_move_rec<const MAX_HEAP_SIZE: usize>(
|
||||
&self,
|
||||
ranking: &mut MoveRanking<MAX_HEAP_SIZE>,
|
||||
current_depth: u32,
|
||||
max_depth: u32,
|
||||
) {
|
||||
let mut pieces = Vec::<Piece, 2>::new();
|
||||
match self.next_move {
|
||||
Player::White => {
|
||||
pieces.push(Piece::SimpleWhitePawn).unwrap();
|
||||
pieces.push(Piece::CrownedWhitePawn).unwrap();
|
||||
}
|
||||
Player::Red => {
|
||||
pieces.push(Piece::SimpleRedPawn).unwrap();
|
||||
pieces.push(Piece::CrownedRedPawn).unwrap();
|
||||
}
|
||||
}
|
||||
self.board.pieces(pieces).for_each(|pos| {
|
||||
self.moves_for_piece(pos).for_each(|mv| {
|
||||
let mut game = self.clone();
|
||||
game.apply_move(&mv).unwrap();
|
||||
let new_score = game.score_for_player(self.next_move);
|
||||
ranking.push(MoveHeapEntry {
|
||||
mv,
|
||||
score: new_score,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_best_move(&self, max_depth: u32) -> Option<Move> {
|
||||
let mut result: Option<(Move, f32)> = None;
|
||||
let available_moves = self.available_moves().count();
|
||||
match available_moves {
|
||||
0 => None,
|
||||
1 => self.available_moves().next(),
|
||||
_ => {
|
||||
for mv in self.available_moves() {
|
||||
let score = self.clone().move_score(&mv, self.next_move, max_depth - 1);
|
||||
if result.is_none() {
|
||||
result = Some((mv, score));
|
||||
} else {
|
||||
if let Some((_, best_score)) = result {
|
||||
if score > best_score {
|
||||
result = Some((mv, score));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.map(|(mv, _)| mv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct MoveHeapEntry {
|
||||
mv: Move,
|
||||
score: u32,
|
||||
}
|
||||
|
||||
impl PartialOrd for MoveHeapEntry {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
|
||||
self.score.partial_cmp(&other.score)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for MoveHeapEntry {
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
self.score.cmp(&other.score)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
Reference in New Issue
Block a user