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 gtk4::glib::{MainContext, Propagation};
|
||||
use gtk4::{self as gtk, gdk::ffi::GDK_BUTTON_PRIMARY};
|
||||
use gtk4::{Application, DrawingArea, prelude::*};
|
||||
use gtk4::{Application, Button, DrawingArea, HeaderBar, prelude::*};
|
||||
use rdraught::{
|
||||
DraughtsBoard, DraughtsGame, Move, Piece, Player, Position, RDraughtApplication,
|
||||
RectangularBoard,
|
||||
};
|
||||
use rsvg::SvgHandle;
|
||||
use std::thread;
|
||||
const SQUARE_SIZE: f64 = 1.0;
|
||||
|
||||
use super::final_dialog;
|
||||
@@ -244,14 +243,64 @@ fn create_game_window(
|
||||
.default_width(800)
|
||||
.default_height(800)
|
||||
.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.
|
||||
let drawing_area = DrawingArea::new();
|
||||
// Add the drawing area to the window.
|
||||
window.set_child(Some(&drawing_area));
|
||||
|
||||
let selected_piece: SharedMutable<Option<Position>> = new_shared_mut(None);
|
||||
let available_moves = new_shared_mut_ref(Vec::<Move>::new());
|
||||
header_bar.pack_start(&undo_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();
|
||||
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.
|
||||
let board_width = SQUARE_SIZE * DraughtsBoard::rows() 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 available_moves = available_moves.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.set_state(gtk::EventSequenceState::Claimed);
|
||||
if let Some(winner) = rd.borrow().game().winner() {
|
||||
@@ -455,7 +506,7 @@ fn create_game_window(
|
||||
(8.0 - p.x() / SQUARE_SIZE) as u8,
|
||||
),
|
||||
};
|
||||
println!("Selected position: {:?}", position);
|
||||
// println!("Selected position: {:?}", position);
|
||||
let piece = {
|
||||
let rd = rd.borrow();
|
||||
let draughts_game = rd.game();
|
||||
@@ -471,7 +522,7 @@ fn create_game_window(
|
||||
for mv in am.into_iter() {
|
||||
if mv.get_end_position() == pos {
|
||||
let mut rd_app = rd.borrow_mut();
|
||||
println!("Applied move: {:?}", mv);
|
||||
// println!("Applied move: {:?}", mv);
|
||||
rd_app.apply_move(mv).unwrap();
|
||||
// let game_copy = rd_app.game().clone();
|
||||
// thread::spawn(move || {
|
||||
@@ -491,6 +542,9 @@ fn create_game_window(
|
||||
}
|
||||
if move_applied {
|
||||
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 {
|
||||
|
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::movement::{Move, MoveDirection};
|
||||
use super::position::Position;
|
||||
use crate::piece::Piece;
|
||||
use crate::player::Player;
|
||||
use heapless::{HistoryBuffer, Vec};
|
||||
use heapless::Vec;
|
||||
|
||||
pub trait RectangularBoard {
|
||||
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 piece = self.get_piece(&start);
|
||||
if let Piece::NoPiece = piece {
|
||||
@@ -129,7 +173,7 @@ impl DraughtsBoard {
|
||||
let player = piece.player().unwrap();
|
||||
if mv.is_movement() {
|
||||
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) {
|
||||
return Err(Error::InvalidMove);
|
||||
}
|
||||
@@ -150,7 +194,8 @@ impl DraughtsBoard {
|
||||
let piece_at_destination = self.get(mv.get_end_position());
|
||||
// Make sure there is no 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
|
||||
if let Piece::NoPiece = captured {
|
||||
return Err(Error::InvalidMove);
|
||||
@@ -160,7 +205,7 @@ impl DraughtsBoard {
|
||||
if captured_piece_player != player {
|
||||
self.set(start, Piece::NoPiece);
|
||||
self.set(end, piece);
|
||||
self.set((end + start) / (2, 2), Piece::NoPiece);
|
||||
self.set(captured_pos, Piece::NoPiece);
|
||||
} else {
|
||||
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 piece = self.board.get_piece(&start);
|
||||
if let Some(player) = piece.player() {
|
||||
if mv.is_movement() {
|
||||
if self.next_move == player {
|
||||
self.board.apply_move(mv)?;
|
||||
self.board.check_and_apply_move(mv)?;
|
||||
self.next_turn();
|
||||
} else {
|
||||
return Err(Error::WrongPlayer);
|
||||
}
|
||||
} 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
|
||||
if self
|
||||
.board
|
||||
@@ -432,7 +486,7 @@ impl DraughtsGame {
|
||||
depth: u32,
|
||||
analyzed_moves: &mut usize,
|
||||
) -> f32 {
|
||||
self.apply_move(mv).unwrap();
|
||||
self.check_and_apply_move(mv).unwrap();
|
||||
if depth != 0 {
|
||||
let mut best_score = None;
|
||||
for mv in self.available_moves() {
|
||||
@@ -599,57 +653,77 @@ impl<'a> Iterator for MoveIterator<'a> {
|
||||
}
|
||||
|
||||
pub struct RDraughtApplication {
|
||||
initial_state: DraughtsGame,
|
||||
game: DraughtsGame,
|
||||
moves: HistoryBuffer<Move, 128>,
|
||||
moves: CircularBuffer<Move, 128>,
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
impl RDraughtApplication {
|
||||
pub fn new(game: DraughtsGame) -> RDraughtApplication {
|
||||
RDraughtApplication {
|
||||
initial_state: game.clone(),
|
||||
game,
|
||||
moves: HistoryBuffer::<Move, 128>::new(),
|
||||
moves: CircularBuffer::<Move, 128>::new(),
|
||||
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> {
|
||||
let mut new_state = self.initial_state.clone();
|
||||
if self.cursor > 0 && !self.moves.is_empty() {
|
||||
self.cursor -= 1;
|
||||
for mv in &self.moves[0..self.cursor] {
|
||||
new_state.apply_move(mv)?;
|
||||
let mut it = self.moves.iter().rev();
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let mv = it.next();
|
||||
if let Some(mv) = mv {
|
||||
if count == self.cursor {
|
||||
self.game.undo_move(&mv);
|
||||
self.cursor += 1;
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
} else {
|
||||
return Err(Error::InvalidMove);
|
||||
}
|
||||
self.game = new_state;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::InvalidMove)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn redo(&mut self) -> Result<(), Error> {
|
||||
let mut new_state = self.initial_state.clone();
|
||||
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 {
|
||||
if self.cursor == 0 {
|
||||
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> {
|
||||
self.game.apply_move(&mv)?;
|
||||
if self.moves.len() == self.moves.capacity() {
|
||||
self.initial_state.apply_move(self.moves.first().unwrap())?;
|
||||
while self.cursor > 0 {
|
||||
self.moves.pop_back();
|
||||
self.cursor -= 1;
|
||||
}
|
||||
self.moves.write(mv);
|
||||
self.cursor += 1;
|
||||
self.game.check_and_apply_move(&mv)?;
|
||||
self.moves.push_evict(mv);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -676,9 +750,44 @@ mod std {
|
||||
#[cfg(feature = "std")]
|
||||
mod tests {
|
||||
extern crate std;
|
||||
use crate::{Move, MoveDirection};
|
||||
|
||||
use super::{DraughtsBoard, Piece, Position};
|
||||
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]
|
||||
fn test_create() {
|
||||
let boards = [
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#![no_std]
|
||||
|
||||
mod circular_buffer;
|
||||
mod constants;
|
||||
pub mod draughts;
|
||||
mod movement;
|
||||
@@ -8,7 +9,7 @@ mod player;
|
||||
mod position;
|
||||
|
||||
pub use draughts::{DraughtsBoard, DraughtsGame, Error, RDraughtApplication, RectangularBoard};
|
||||
pub use movement::Move;
|
||||
pub use movement::{Move, MoveDirection};
|
||||
pub use piece::Piece;
|
||||
pub use player::Player;
|
||||
pub use position::Position;
|
||||
|
Reference in New Issue
Block a user