added undo/redo functionality
This commit is contained in:
@@ -4,13 +4,12 @@ use gdk4::cairo::{Context as CairoContext, Matrix, Rectangle};
|
|||||||
use glib::ExitCode;
|
use glib::ExitCode;
|
||||||
use gtk4::glib::{MainContext, Propagation};
|
use gtk4::glib::{MainContext, Propagation};
|
||||||
use gtk4::{self as gtk, gdk::ffi::GDK_BUTTON_PRIMARY};
|
use gtk4::{self as gtk, gdk::ffi::GDK_BUTTON_PRIMARY};
|
||||||
use gtk4::{Application, DrawingArea, prelude::*};
|
use gtk4::{Application, Button, DrawingArea, HeaderBar, prelude::*};
|
||||||
use rdraught::{
|
use rdraught::{
|
||||||
DraughtsBoard, DraughtsGame, Move, Piece, Player, Position, RDraughtApplication,
|
DraughtsBoard, DraughtsGame, Move, Piece, Player, Position, RDraughtApplication,
|
||||||
RectangularBoard,
|
RectangularBoard,
|
||||||
};
|
};
|
||||||
use rsvg::SvgHandle;
|
use rsvg::SvgHandle;
|
||||||
use std::thread;
|
|
||||||
const SQUARE_SIZE: f64 = 1.0;
|
const SQUARE_SIZE: f64 = 1.0;
|
||||||
|
|
||||||
use super::final_dialog;
|
use super::final_dialog;
|
||||||
@@ -244,14 +243,64 @@ fn create_game_window(
|
|||||||
.default_width(800)
|
.default_width(800)
|
||||||
.default_height(800)
|
.default_height(800)
|
||||||
.build();
|
.build();
|
||||||
|
let header_bar = HeaderBar::new();
|
||||||
|
window.set_titlebar(Some(&header_bar));
|
||||||
|
|
||||||
|
let undo_button = Button::new();
|
||||||
|
undo_button.set_icon_name("edit-undo-symbolic");
|
||||||
|
undo_button.set_sensitive(false);
|
||||||
|
|
||||||
|
let redo_button = Button::new();
|
||||||
|
redo_button.set_icon_name("edit-redo-symbolic");
|
||||||
|
redo_button.set_sensitive(false);
|
||||||
|
|
||||||
|
let selected_piece: SharedMutable<Option<Position>> = new_shared_mut(None);
|
||||||
|
let available_moves = new_shared_mut_ref(Vec::<Move>::new());
|
||||||
// Create a DrawingArea widget where we will draw the chessboard.
|
// Create a DrawingArea widget where we will draw the chessboard.
|
||||||
let drawing_area = DrawingArea::new();
|
let drawing_area = DrawingArea::new();
|
||||||
// Add the drawing area to the window.
|
// Add the drawing area to the window.
|
||||||
window.set_child(Some(&drawing_area));
|
window.set_child(Some(&drawing_area));
|
||||||
|
|
||||||
let selected_piece: SharedMutable<Option<Position>> = new_shared_mut(None);
|
header_bar.pack_start(&undo_button);
|
||||||
let available_moves = new_shared_mut_ref(Vec::<Move>::new());
|
{
|
||||||
|
let da = drawing_area.clone();
|
||||||
|
let rd = rd.clone();
|
||||||
|
let undo_btn_clone = undo_button.clone();
|
||||||
|
let redo_btn_clone = redo_button.clone();
|
||||||
|
let selected_piece = selected_piece.clone();
|
||||||
|
let available_moves = available_moves.clone();
|
||||||
|
undo_button.connect_clicked(move |_| {
|
||||||
|
let mut rd = rd.borrow_mut();
|
||||||
|
if rd.can_undo() {
|
||||||
|
rd.undo().ok();
|
||||||
|
redo_btn_clone.set_sensitive(rd.can_redo());
|
||||||
|
undo_btn_clone.set_sensitive(rd.can_undo());
|
||||||
|
selected_piece.set(None);
|
||||||
|
available_moves.borrow_mut().clear();
|
||||||
|
da.queue_draw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
header_bar.pack_start(&redo_button);
|
||||||
|
{
|
||||||
|
let da = drawing_area.clone();
|
||||||
|
let rd = rd.clone();
|
||||||
|
let undo_btn_clone = undo_button.clone();
|
||||||
|
let redo_btn_clone = redo_button.clone();
|
||||||
|
let selected_piece = selected_piece.clone();
|
||||||
|
let available_moves = available_moves.clone();
|
||||||
|
redo_button.connect_clicked(move |_| {
|
||||||
|
let mut rd = rd.borrow_mut();
|
||||||
|
if rd.can_redo() {
|
||||||
|
rd.redo().ok();
|
||||||
|
redo_btn_clone.set_sensitive(rd.can_redo());
|
||||||
|
undo_btn_clone.set_sensitive(rd.can_undo());
|
||||||
|
selected_piece.set(None);
|
||||||
|
available_moves.borrow_mut().clear();
|
||||||
|
da.queue_draw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
// Get the allocation information for the widget.
|
// Get the allocation information for the widget.
|
||||||
let board_width = SQUARE_SIZE * DraughtsBoard::rows() as f64;
|
let board_width = SQUARE_SIZE * DraughtsBoard::rows() as f64;
|
||||||
let board_height = SQUARE_SIZE * DraughtsBoard::columns() as f64;
|
let board_height = SQUARE_SIZE * DraughtsBoard::columns() as f64;
|
||||||
@@ -429,6 +478,8 @@ fn create_game_window(
|
|||||||
let drawing_area = drawing_area.clone();
|
let drawing_area = drawing_area.clone();
|
||||||
let available_moves = available_moves.clone();
|
let available_moves = available_moves.clone();
|
||||||
let window = window.clone();
|
let window = window.clone();
|
||||||
|
let undo_btn_clone = undo_button.clone();
|
||||||
|
let redo_btn_clone = redo_button.clone();
|
||||||
gesture.connect_pressed(move |gesture, _, x, y| {
|
gesture.connect_pressed(move |gesture, _, x, y| {
|
||||||
gesture.set_state(gtk::EventSequenceState::Claimed);
|
gesture.set_state(gtk::EventSequenceState::Claimed);
|
||||||
if let Some(winner) = rd.borrow().game().winner() {
|
if let Some(winner) = rd.borrow().game().winner() {
|
||||||
@@ -455,7 +506,7 @@ fn create_game_window(
|
|||||||
(8.0 - p.x() / SQUARE_SIZE) as u8,
|
(8.0 - p.x() / SQUARE_SIZE) as u8,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
println!("Selected position: {:?}", position);
|
// println!("Selected position: {:?}", position);
|
||||||
let piece = {
|
let piece = {
|
||||||
let rd = rd.borrow();
|
let rd = rd.borrow();
|
||||||
let draughts_game = rd.game();
|
let draughts_game = rd.game();
|
||||||
@@ -471,7 +522,7 @@ fn create_game_window(
|
|||||||
for mv in am.into_iter() {
|
for mv in am.into_iter() {
|
||||||
if mv.get_end_position() == pos {
|
if mv.get_end_position() == pos {
|
||||||
let mut rd_app = rd.borrow_mut();
|
let mut rd_app = rd.borrow_mut();
|
||||||
println!("Applied move: {:?}", mv);
|
// println!("Applied move: {:?}", mv);
|
||||||
rd_app.apply_move(mv).unwrap();
|
rd_app.apply_move(mv).unwrap();
|
||||||
// let game_copy = rd_app.game().clone();
|
// let game_copy = rd_app.game().clone();
|
||||||
// thread::spawn(move || {
|
// thread::spawn(move || {
|
||||||
@@ -491,6 +542,9 @@ fn create_game_window(
|
|||||||
}
|
}
|
||||||
if move_applied {
|
if move_applied {
|
||||||
selected_piece.set(None);
|
selected_piece.set(None);
|
||||||
|
let rd = rd.borrow();
|
||||||
|
undo_btn_clone.set_sensitive(rd.can_undo());
|
||||||
|
redo_btn_clone.set_sensitive(rd.can_redo());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !move_applied {
|
if !move_applied {
|
||||||
|
297
rdraught/src/circular_buffer.rs
Normal file
297
rdraught/src/circular_buffer.rs
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
use core::mem::MaybeUninit;
|
||||||
|
use core::result::Result;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
BufferIsFull,
|
||||||
|
BufferIsEmpty,
|
||||||
|
BufferHasZeroSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CircularBuffer<T: Sized, const SIZE: usize> {
|
||||||
|
buffer: [MaybeUninit<T>; SIZE],
|
||||||
|
front: usize,
|
||||||
|
rear: usize,
|
||||||
|
fill: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sized, const SIZE: usize> CircularBuffer<T, SIZE> {
|
||||||
|
pub fn new() -> CircularBuffer<T, SIZE> {
|
||||||
|
CircularBuffer {
|
||||||
|
buffer: [const { MaybeUninit::uninit() }; SIZE],
|
||||||
|
front: 0,
|
||||||
|
rear: 0,
|
||||||
|
fill: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, element: T) -> Result<(), Error> {
|
||||||
|
if self.len() == SIZE {
|
||||||
|
Err(Error::BufferIsFull)
|
||||||
|
} else {
|
||||||
|
self.buffer[self.rear].write(element);
|
||||||
|
self.rear = (self.rear + 1) % SIZE;
|
||||||
|
self.fill += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_evict(&mut self, element: T) -> Option<T> {
|
||||||
|
let result = if self.len() == SIZE {
|
||||||
|
self.pop_front()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.buffer[self.rear].write(element);
|
||||||
|
self.rear = (self.rear + 1) % SIZE;
|
||||||
|
self.fill += 1;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_front(&mut self) -> Option<T> {
|
||||||
|
if self.len() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let mut result = MaybeUninit::uninit();
|
||||||
|
core::mem::swap(&mut result, &mut self.buffer[self.front]);
|
||||||
|
self.front = (self.front + 1) % SIZE;
|
||||||
|
self.fill -= 1;
|
||||||
|
Some(unsafe { result.assume_init() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_back(&mut self) -> Option<T> {
|
||||||
|
if self.len() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let mut result = MaybeUninit::uninit();
|
||||||
|
core::mem::swap(&mut result, &mut self.buffer[self.rear - 1]);
|
||||||
|
self.rear = (self.rear - 1) % SIZE;
|
||||||
|
self.fill -= 1;
|
||||||
|
Some(unsafe { result.assume_init() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.fill
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capacity(&self) -> usize {
|
||||||
|
SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &T> {
|
||||||
|
CircularBufferIteratorRef::<T, SIZE> {
|
||||||
|
cb: self,
|
||||||
|
count: 0,
|
||||||
|
count_back: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
|
||||||
|
CircularBufferIteratorMutRef::<T, SIZE> {
|
||||||
|
cb: self,
|
||||||
|
count: 0,
|
||||||
|
count_back: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn first(&self) -> Option<&T> {
|
||||||
|
if self.len() > 0 {
|
||||||
|
unsafe { Some(self.buffer[self.front].assume_init_ref()) }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last(&self) -> Option<&T> {
|
||||||
|
if self.len() > 0 {
|
||||||
|
unsafe { Some(self.buffer[self.rear - 1].assume_init_ref()) }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CircularBufferIterator<T: Sized, const SIZE: usize> {
|
||||||
|
buffer: CircularBuffer<T, SIZE>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sized, const SIZE: usize> Iterator for CircularBufferIterator<T, SIZE> {
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.buffer.pop_front()
|
||||||
|
}
|
||||||
|
type Item = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sized, const SIZE: usize> IntoIterator for CircularBuffer<T, SIZE> {
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
CircularBufferIterator { buffer: self }
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntoIter = CircularBufferIterator<T, SIZE>;
|
||||||
|
type Item = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CircularBufferIteratorRef<'a, T: Sized, const SIZE: usize> {
|
||||||
|
cb: &'a CircularBuffer<T, SIZE>,
|
||||||
|
count: usize,
|
||||||
|
count_back: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Sized, const SIZE: usize> Iterator for CircularBufferIteratorRef<'a, T, SIZE> {
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.count + self.count_back == self.cb.len() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let i = (self.cb.front + self.count) % SIZE;
|
||||||
|
let result = Some(unsafe { self.cb.buffer[i].assume_init_ref() });
|
||||||
|
self.count += 1;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type Item = &'a T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Sized, const SIZE: usize> DoubleEndedIterator
|
||||||
|
for CircularBufferIteratorRef<'a, T, SIZE>
|
||||||
|
{
|
||||||
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.count_back == self.cb.len() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let i = (SIZE + self.cb.rear - 1 - self.count_back) % SIZE;
|
||||||
|
let result = Some(unsafe { self.cb.buffer[i].assume_init_ref() });
|
||||||
|
self.count_back += 1;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Sized, const SIZE: usize> IntoIterator for &'a CircularBuffer<T, SIZE> {
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
CircularBufferIteratorRef {
|
||||||
|
cb: self,
|
||||||
|
count: 0,
|
||||||
|
count_back: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntoIter = CircularBufferIteratorRef<'a, T, SIZE>;
|
||||||
|
type Item = &'a T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CircularBufferIteratorMutRef<'a, T: Sized, const SIZE: usize> {
|
||||||
|
cb: &'a mut CircularBuffer<T, SIZE>,
|
||||||
|
count: usize,
|
||||||
|
count_back: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Sized, const SIZE: usize> Iterator for CircularBufferIteratorMutRef<'a, T, SIZE> {
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.count + self.count_back == self.cb.len() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let result = unsafe {
|
||||||
|
let i = (self.cb.front + self.count) % SIZE;
|
||||||
|
Some(core::mem::transmute::<&mut T, &'a mut T>(
|
||||||
|
self.cb.buffer[i].assume_init_mut(),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
self.count += 1;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item = &'a mut T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Sized, const SIZE: usize> DoubleEndedIterator
|
||||||
|
for CircularBufferIteratorMutRef<'a, T, SIZE>
|
||||||
|
{
|
||||||
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.count + self.count_back == self.cb.len() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let result = unsafe {
|
||||||
|
let i = (SIZE + self.cb.rear - 1 - self.count_back) % SIZE;
|
||||||
|
Some(core::mem::transmute::<&mut T, &'a mut T>(
|
||||||
|
self.cb.buffer[i].assume_init_mut(),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
self.count_back += 1;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Sized, const SIZE: usize> IntoIterator for &'a mut CircularBuffer<T, SIZE> {
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
CircularBufferIteratorMutRef {
|
||||||
|
cb: self,
|
||||||
|
count: 0,
|
||||||
|
count_back: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntoIter = CircularBufferIteratorMutRef<'a, T, SIZE>;
|
||||||
|
type Item = &'a mut T;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
const SZ: usize = 10;
|
||||||
|
let mut cb = CircularBuffer::<i32, SZ>::new();
|
||||||
|
for i in 0..SZ {
|
||||||
|
let result = cb.push(i as i32);
|
||||||
|
assert_eq!(true, result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check the buffer is full
|
||||||
|
assert_eq!(SZ, cb.len());
|
||||||
|
|
||||||
|
//The buffer contains the elements that we've pushed
|
||||||
|
for (index, elem) in (&cb).into_iter().enumerate() {
|
||||||
|
assert_eq!(index as i32, *elem);
|
||||||
|
}
|
||||||
|
let result = cb.push(10);
|
||||||
|
//Cannot push in a full buffer
|
||||||
|
assert_eq!(true, result.is_err());
|
||||||
|
|
||||||
|
//Check push_evict returns the first element
|
||||||
|
let result = cb.push_evict(10);
|
||||||
|
assert_eq!(Some(0), result);
|
||||||
|
|
||||||
|
//Check pop_front returns the second element
|
||||||
|
let second = cb.pop_front();
|
||||||
|
assert_eq!(Some(1), second);
|
||||||
|
//Check the buffer is not full
|
||||||
|
assert_eq!(SZ - 1, cb.len());
|
||||||
|
|
||||||
|
//Check pop_front returns the third element
|
||||||
|
let second = cb.pop_front();
|
||||||
|
|
||||||
|
assert_eq!(Some(2), second);
|
||||||
|
//Check the buffer is not full
|
||||||
|
assert_eq!(SZ - 2, cb.len());
|
||||||
|
|
||||||
|
//Replace all the elements with their index
|
||||||
|
for (n, i) in cb.iter_mut().enumerate() {
|
||||||
|
*i = n as i32;
|
||||||
|
}
|
||||||
|
for (n, i) in cb.iter().enumerate() {
|
||||||
|
assert_eq!(n as i32, *i);
|
||||||
|
}
|
||||||
|
for (n, i) in cb.iter().rev().enumerate() {
|
||||||
|
assert_eq!((cb.len() - n - 1) as i32, *i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,9 +1,10 @@
|
|||||||
|
use super::circular_buffer::CircularBuffer;
|
||||||
use super::constants::{BITS_PER_POSITION, POSITIONS, POSITIONS_PER_ROW};
|
use super::constants::{BITS_PER_POSITION, POSITIONS, POSITIONS_PER_ROW};
|
||||||
use super::movement::{Move, MoveDirection};
|
use super::movement::{Move, MoveDirection};
|
||||||
use super::position::Position;
|
use super::position::Position;
|
||||||
use crate::piece::Piece;
|
use crate::piece::Piece;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
use heapless::{HistoryBuffer, Vec};
|
use heapless::Vec;
|
||||||
|
|
||||||
pub trait RectangularBoard {
|
pub trait RectangularBoard {
|
||||||
fn rows() -> usize;
|
fn rows() -> usize;
|
||||||
@@ -120,7 +121,50 @@ impl DraughtsBoard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_move(&mut self, mv: &Move) -> Result<(), Error> {
|
fn undo_move(&mut self, mv: &Move) {
|
||||||
|
let end_pos = mv.get_end_position();
|
||||||
|
let p = self.get(end_pos);
|
||||||
|
self.set(end_pos, Piece::NoPiece);
|
||||||
|
self.set(mv.start_position(), p);
|
||||||
|
if mv.is_capture() {
|
||||||
|
let captured_position = (mv.start_position() + mv.get_end_position()) / (2, 2);
|
||||||
|
let captured_piece = match p.player().unwrap() {
|
||||||
|
Player::Red => {
|
||||||
|
if mv.crowned_captured() {
|
||||||
|
Piece::CrownedWhitePawn
|
||||||
|
} else {
|
||||||
|
Piece::SimpleWhitePawn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Player::White => {
|
||||||
|
if mv.crowned_captured() {
|
||||||
|
Piece::CrownedRedPawn
|
||||||
|
} else {
|
||||||
|
Piece::SimpleRedPawn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.set(captured_position, captured_piece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_move(&mut self, mv: &Move) {
|
||||||
|
let start = mv.start_position();
|
||||||
|
let piece = self.get_piece(&start);
|
||||||
|
if mv.is_movement() {
|
||||||
|
let end = mv.get_end_position();
|
||||||
|
self.set(start, Piece::NoPiece);
|
||||||
|
self.set(end, piece);
|
||||||
|
} else {
|
||||||
|
let end = mv.get_end_position();
|
||||||
|
let captured_pos = (start + end) / (2, 2);
|
||||||
|
self.set(start, Piece::NoPiece);
|
||||||
|
self.set(end, piece);
|
||||||
|
self.set(captured_pos, Piece::NoPiece);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_and_apply_move(&mut self, mv: &Move) -> Result<(), Error> {
|
||||||
let start = mv.start_position();
|
let start = mv.start_position();
|
||||||
let piece = self.get_piece(&start);
|
let piece = self.get_piece(&start);
|
||||||
if let Piece::NoPiece = piece {
|
if let Piece::NoPiece = piece {
|
||||||
@@ -129,7 +173,7 @@ impl DraughtsBoard {
|
|||||||
let player = piece.player().unwrap();
|
let player = piece.player().unwrap();
|
||||||
if mv.is_movement() {
|
if mv.is_movement() {
|
||||||
let end = mv.get_end_position();
|
let end = mv.get_end_position();
|
||||||
// Make sure the move ends in a vlid position
|
// Make sure the move ends in a valid position
|
||||||
if !DraughtsBoard::is_position_valid(end) {
|
if !DraughtsBoard::is_position_valid(end) {
|
||||||
return Err(Error::InvalidMove);
|
return Err(Error::InvalidMove);
|
||||||
}
|
}
|
||||||
@@ -150,7 +194,8 @@ impl DraughtsBoard {
|
|||||||
let piece_at_destination = self.get(mv.get_end_position());
|
let piece_at_destination = self.get(mv.get_end_position());
|
||||||
// Make sure there is no piece at destination
|
// Make sure there is no piece at destination
|
||||||
if let Piece::NoPiece = piece_at_destination {
|
if let Piece::NoPiece = piece_at_destination {
|
||||||
let captured = self.get((end + start) / (2, 2));
|
let captured_pos = (start + end) / (2, 2);
|
||||||
|
let captured = self.get(captured_pos);
|
||||||
// Make sure there is a piece to be captured
|
// Make sure there is a piece to be captured
|
||||||
if let Piece::NoPiece = captured {
|
if let Piece::NoPiece = captured {
|
||||||
return Err(Error::InvalidMove);
|
return Err(Error::InvalidMove);
|
||||||
@@ -160,7 +205,7 @@ impl DraughtsBoard {
|
|||||||
if captured_piece_player != player {
|
if captured_piece_player != player {
|
||||||
self.set(start, Piece::NoPiece);
|
self.set(start, Piece::NoPiece);
|
||||||
self.set(end, piece);
|
self.set(end, piece);
|
||||||
self.set((end + start) / (2, 2), Piece::NoPiece);
|
self.set(captured_pos, Piece::NoPiece);
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::InvalidMove);
|
return Err(Error::InvalidMove);
|
||||||
}
|
}
|
||||||
@@ -338,19 +383,28 @@ impl DraughtsGame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_move(&mut self, mv: &Move) -> Result<(), Error> {
|
fn undo_move(&mut self, mv: &Move) {
|
||||||
|
self.board.undo_move(mv);
|
||||||
|
self.next_turn();
|
||||||
|
}
|
||||||
|
fn redo_move(&mut self, mv: &Move) {
|
||||||
|
self.board.apply_move(mv);
|
||||||
|
self.next_turn();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_and_apply_move(&mut self, mv: &Move) -> Result<(), Error> {
|
||||||
let start = mv.start_position();
|
let start = mv.start_position();
|
||||||
let piece = self.board.get_piece(&start);
|
let piece = self.board.get_piece(&start);
|
||||||
if let Some(player) = piece.player() {
|
if let Some(player) = piece.player() {
|
||||||
if mv.is_movement() {
|
if mv.is_movement() {
|
||||||
if self.next_move == player {
|
if self.next_move == player {
|
||||||
self.board.apply_move(mv)?;
|
self.board.check_and_apply_move(mv)?;
|
||||||
self.next_turn();
|
self.next_turn();
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::WrongPlayer);
|
return Err(Error::WrongPlayer);
|
||||||
}
|
}
|
||||||
} else if self.next_move == player {
|
} else if self.next_move == player {
|
||||||
self.board.apply_move(mv)?;
|
self.board.check_and_apply_move(mv)?;
|
||||||
// Check if more captures are available for the current piece
|
// Check if more captures are available for the current piece
|
||||||
if self
|
if self
|
||||||
.board
|
.board
|
||||||
@@ -432,7 +486,7 @@ impl DraughtsGame {
|
|||||||
depth: u32,
|
depth: u32,
|
||||||
analyzed_moves: &mut usize,
|
analyzed_moves: &mut usize,
|
||||||
) -> f32 {
|
) -> f32 {
|
||||||
self.apply_move(mv).unwrap();
|
self.check_and_apply_move(mv).unwrap();
|
||||||
if depth != 0 {
|
if depth != 0 {
|
||||||
let mut best_score = None;
|
let mut best_score = None;
|
||||||
for mv in self.available_moves() {
|
for mv in self.available_moves() {
|
||||||
@@ -599,57 +653,77 @@ impl<'a> Iterator for MoveIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct RDraughtApplication {
|
pub struct RDraughtApplication {
|
||||||
initial_state: DraughtsGame,
|
|
||||||
game: DraughtsGame,
|
game: DraughtsGame,
|
||||||
moves: HistoryBuffer<Move, 128>,
|
moves: CircularBuffer<Move, 128>,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RDraughtApplication {
|
impl RDraughtApplication {
|
||||||
pub fn new(game: DraughtsGame) -> RDraughtApplication {
|
pub fn new(game: DraughtsGame) -> RDraughtApplication {
|
||||||
RDraughtApplication {
|
RDraughtApplication {
|
||||||
initial_state: game.clone(),
|
|
||||||
game,
|
game,
|
||||||
moves: HistoryBuffer::<Move, 128>::new(),
|
moves: CircularBuffer::<Move, 128>::new(),
|
||||||
cursor: 0usize,
|
cursor: 0usize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn can_undo(&self) -> bool {
|
||||||
|
self.moves.len() > self.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn can_redo(&self) -> bool {
|
||||||
|
self.cursor > 0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn undo(&mut self) -> Result<(), Error> {
|
pub fn undo(&mut self) -> Result<(), Error> {
|
||||||
let mut new_state = self.initial_state.clone();
|
let mut it = self.moves.iter().rev();
|
||||||
if self.cursor > 0 && !self.moves.is_empty() {
|
let mut count = 0;
|
||||||
self.cursor -= 1;
|
loop {
|
||||||
for mv in &self.moves[0..self.cursor] {
|
let mv = it.next();
|
||||||
new_state.apply_move(mv)?;
|
if let Some(mv) = mv {
|
||||||
|
if count == self.cursor {
|
||||||
|
self.game.undo_move(&mv);
|
||||||
|
self.cursor += 1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
self.game = new_state;
|
count += 1;
|
||||||
Ok(())
|
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InvalidMove)
|
return Err(Error::InvalidMove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn redo(&mut self) -> Result<(), Error> {
|
pub fn redo(&mut self) -> Result<(), Error> {
|
||||||
let mut new_state = self.initial_state.clone();
|
if self.cursor == 0 {
|
||||||
if self.cursor < self.moves.len() {
|
|
||||||
for mv in &self.moves[0..self.cursor] {
|
|
||||||
new_state.apply_move(mv)?;
|
|
||||||
}
|
|
||||||
self.game = new_state;
|
|
||||||
self.cursor += 1;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error::InvalidMove)
|
Err(Error::InvalidMove)
|
||||||
|
} else {
|
||||||
|
let mut it = self.moves.iter().rev();
|
||||||
|
let mut count = 0;
|
||||||
|
loop {
|
||||||
|
let mv = it.next();
|
||||||
|
if let Some(mv) = mv {
|
||||||
|
if count + 1 == self.cursor {
|
||||||
|
self.game.redo_move(&mv);
|
||||||
|
self.cursor -= 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
count += 1;
|
||||||
|
} else {
|
||||||
|
return Err(Error::InvalidMove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_move(&mut self, mv: Move) -> Result<(), Error> {
|
pub fn apply_move(&mut self, mv: Move) -> Result<(), Error> {
|
||||||
self.game.apply_move(&mv)?;
|
while self.cursor > 0 {
|
||||||
if self.moves.len() == self.moves.capacity() {
|
self.moves.pop_back();
|
||||||
self.initial_state.apply_move(self.moves.first().unwrap())?;
|
self.cursor -= 1;
|
||||||
}
|
}
|
||||||
self.moves.write(mv);
|
self.game.check_and_apply_move(&mv)?;
|
||||||
self.cursor += 1;
|
self.moves.push_evict(mv);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,9 +750,44 @@ mod std {
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
mod tests {
|
mod tests {
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
use crate::{Move, MoveDirection};
|
||||||
|
|
||||||
use super::{DraughtsBoard, Piece, Position};
|
use super::{DraughtsBoard, Piece, Position};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_undo_redo() {
|
||||||
|
let pieces = [
|
||||||
|
(Position::new(0, 4), Piece::CrownedWhitePawn),
|
||||||
|
(Position::new(1, 3), Piece::SimpleRedPawn),
|
||||||
|
(Position::new(5, 5), Piece::SimpleWhitePawn),
|
||||||
|
(Position::new(4, 4), Piece::SimpleRedPawn),
|
||||||
|
];
|
||||||
|
|
||||||
|
let map = pieces
|
||||||
|
.into_iter()
|
||||||
|
.map(|(pos, piece)| (pos.unwrap(), piece))
|
||||||
|
.collect::<HashMap<Position, Piece>>();
|
||||||
|
let board = DraughtsBoard::new(|p| match map.get(&p) {
|
||||||
|
None => Piece::NoPiece,
|
||||||
|
Some(piece) => *piece,
|
||||||
|
});
|
||||||
|
|
||||||
|
let moves = [
|
||||||
|
Move::capture(Position::new(0, 4).unwrap(), MoveDirection::NW, false),
|
||||||
|
Move::movement(Position::new(0, 4).unwrap(), MoveDirection::NE),
|
||||||
|
Move::capture(Position::new(5, 5).unwrap(), MoveDirection::SW, false),
|
||||||
|
Move::movement(Position::new(4, 4).unwrap(), MoveDirection::NW),
|
||||||
|
];
|
||||||
|
for mv in moves {
|
||||||
|
let mut board_clone = board.clone();
|
||||||
|
board_clone.apply_move(&mv);
|
||||||
|
assert_ne!(board, board_clone);
|
||||||
|
board_clone.undo_move(&mv);
|
||||||
|
assert_eq!(board, board_clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create() {
|
fn test_create() {
|
||||||
let boards = [
|
let boards = [
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
mod circular_buffer;
|
||||||
mod constants;
|
mod constants;
|
||||||
pub mod draughts;
|
pub mod draughts;
|
||||||
mod movement;
|
mod movement;
|
||||||
@@ -8,7 +9,7 @@ mod player;
|
|||||||
mod position;
|
mod position;
|
||||||
|
|
||||||
pub use draughts::{DraughtsBoard, DraughtsGame, Error, RDraughtApplication, RectangularBoard};
|
pub use draughts::{DraughtsBoard, DraughtsGame, Error, RDraughtApplication, RectangularBoard};
|
||||||
pub use movement::Move;
|
pub use movement::{Move, MoveDirection};
|
||||||
pub use piece::Piece;
|
pub use piece::Piece;
|
||||||
pub use player::Player;
|
pub use player::Player;
|
||||||
pub use position::Position;
|
pub use position::Position;
|
||||||
|
Reference in New Issue
Block a user