fixed undo/redo bugs

renamed rdraught-ui to rdraught-gtk
This commit is contained in:
2025-07-03 10:37:15 +08:00
parent dd0777ff9a
commit e5182c26f3
16 changed files with 293 additions and 70 deletions

View File

@@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["rdraught", "rdraught-cli", "rdraught-ui", "rdraught-w4"] members = ["rdraught", "rdraught-cli", "rdraught-gtk", "rdraught-w4"]
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "rdraught-ui" name = "rdraught-gtk"
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true
homepage.workspace = true homepage.workspace = true
@@ -18,10 +18,10 @@ gio.workspace = true
glib.workspace = true glib.workspace = true
[lib] [lib]
name = "rdraught_ui" name = "rdraught_gtk"
crate-type = ["lib"] crate-type = ["lib"]
bench = false bench = false
[[bin]] [[bin]]
name = "rdraught-ui" name = "rdraught-gtk"
path = "src/main.rs" path = "src/main.rs"

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -3,5 +3,5 @@ use rdraught::draughts::DraughtsGame;
fn main() -> ExitCode { fn main() -> ExitCode {
let game = DraughtsGame::default(); let game = DraughtsGame::default();
rdraught_ui::run(game) rdraught_gtk::run(game)
} }

View File

@@ -1,6 +1,6 @@
use super::circular_buffer::CircularBuffer; 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::{LoggedMove, 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;
@@ -89,7 +89,7 @@ impl DraughtsBoard {
} }
fn check_move_valid(&self, mv: &Move) -> Result<(), Error> { fn check_move_valid(&self, mv: &Move) -> Result<(), Error> {
let start = mv.start_position(); let start = mv.get_start_position();
if mv.is_movement() { if mv.is_movement() {
let mut move_is_possible = false; let mut move_is_possible = false;
for possible_move in self.moves_for_piece(start, false) { for possible_move in self.moves_for_piece(start, false) {
@@ -121,11 +121,21 @@ impl DraughtsBoard {
} }
} }
fn undo_move(&mut self, mv: &Move) { fn undo_move(&mut self, mv: &LoggedMove) {
let end_pos = mv.get_end_position(); let end_pos = mv.get_end_position();
let p = self.get(end_pos); let p = self.get(end_pos);
//Revert promotion if any
let initial_piece = if mv.pawn_promoted() {
match p.player() {
Some(Player::White) => Piece::SimpleWhitePawn,
Some(Player::Red) => Piece::SimpleRedPawn,
None => Piece::NoPiece,
}
} else {
p
};
self.set(end_pos, Piece::NoPiece); self.set(end_pos, Piece::NoPiece);
self.set(mv.start_position(), p); self.set(mv.start_position(), initial_piece);
if mv.is_capture() { if mv.is_capture() {
let captured_position = (mv.start_position() + mv.get_end_position()) / (2, 2); let captured_position = (mv.start_position() + mv.get_end_position()) / (2, 2);
let captured_piece = match p.player().unwrap() { let captured_piece = match p.player().unwrap() {
@@ -148,24 +158,33 @@ impl DraughtsBoard {
} }
} }
fn apply_move(&mut self, mv: &Move) { fn apply_move(&mut self, mv: &LoggedMove) {
let start = mv.start_position(); let start = mv.start_position();
let piece = self.get_piece(&start); let piece = self.get_piece(&start);
let final_piece = if mv.pawn_promoted() {
match piece.player() {
Some(Player::Red) => Piece::CrownedRedPawn,
Some(Player::White) => Piece::CrownedWhitePawn,
None => Piece::NoPiece,
}
} else {
piece
};
if mv.is_movement() { if mv.is_movement() {
let end = mv.get_end_position(); let end = mv.get_end_position();
self.set(start, Piece::NoPiece); self.set(start, Piece::NoPiece);
self.set(end, piece); self.set(end, final_piece);
} else { } else {
let end = mv.get_end_position(); let end = mv.get_end_position();
let captured_pos = (start + end) / (2, 2); let captured_pos = (start + end) / (2, 2);
self.set(start, Piece::NoPiece); self.set(start, Piece::NoPiece);
self.set(end, piece); self.set(end, final_piece);
self.set(captured_pos, Piece::NoPiece); self.set(captured_pos, Piece::NoPiece);
}; };
} }
fn check_and_apply_move(&mut self, mv: &Move) -> Result<(), Error> { fn check_and_apply_move(&mut self, mv: &Move) -> Result<(), Error> {
let start = mv.start_position(); let start = mv.get_start_position();
let piece = self.get_piece(&start); let piece = self.get_piece(&start);
if let Piece::NoPiece = piece { if let Piece::NoPiece = piece {
Err(Error::InvalidMove) Err(Error::InvalidMove)
@@ -383,17 +402,22 @@ impl DraughtsGame {
} }
} }
fn undo_move(&mut self, mv: &Move) { fn undo_move(&mut self, mv: &LoggedMove) {
self.board.undo_move(mv); self.board.undo_move(mv);
if !mv.is_capture() || !mv.multi_capture() {
self.next_turn(); self.next_turn();
} }
fn redo_move(&mut self, mv: &Move) { }
fn redo_move(&mut self, mv: &LoggedMove) {
self.board.apply_move(mv); self.board.apply_move(mv);
if !mv.is_capture() || !mv.multi_capture() {
self.next_turn(); self.next_turn();
} }
}
pub fn check_and_apply_move(&mut self, mv: &Move) -> Result<(), Error> { pub fn check_and_apply_move(&mut self, mv: &Move) -> Result<(), Error> {
let start = mv.start_position(); let start = mv.get_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() {
@@ -631,11 +655,7 @@ impl<'a> Iterator for MoveIterator<'a> {
} }
_ => { _ => {
if piece.player() != piece_at_next_position.player() { if piece.player() != piece_at_next_position.player() {
let capture = Move::capture( let capture = Move::capture(self.position, direction);
self.position,
direction,
piece_at_next_position.is_crowned(),
);
let next_position = capture.get_end_position(); let next_position = capture.get_end_position();
if DraughtsBoard::is_position_valid(next_position) { if DraughtsBoard::is_position_valid(next_position) {
let piece_at_end_position = self.board.get(next_position); let piece_at_end_position = self.board.get(next_position);
@@ -654,7 +674,7 @@ impl<'a> Iterator for MoveIterator<'a> {
pub struct RDraughtApplication { pub struct RDraughtApplication {
game: DraughtsGame, game: DraughtsGame,
moves: CircularBuffer<Move, 128>, moves: CircularBuffer<LoggedMove, 128>,
cursor: usize, cursor: usize,
} }
@@ -662,7 +682,7 @@ impl RDraughtApplication {
pub fn new(game: DraughtsGame) -> RDraughtApplication { pub fn new(game: DraughtsGame) -> RDraughtApplication {
RDraughtApplication { RDraughtApplication {
game, game,
moves: CircularBuffer::<Move, 128>::new(), moves: CircularBuffer::<LoggedMove, 128>::new(),
cursor: 0usize, cursor: 0usize,
} }
} }
@@ -722,8 +742,40 @@ impl RDraughtApplication {
self.moves.pop_back(); self.moves.pop_back();
self.cursor -= 1; self.cursor -= 1;
} }
let initial_piece = self.game.piece_at(mv.get_start_position());
let end_position = mv.get_end_position();
let crowned_capture = if let Move::Capture {
start: _,
direction: _,
} = mv
{
self.game
.piece_at((mv.get_start_position() + end_position) / (2, 2))
.is_crowned()
} else {
false
};
let next_player_move = self.game.next_move;
self.game.check_and_apply_move(&mv)?; self.game.check_and_apply_move(&mv)?;
self.moves.push_evict(mv); let final_piece = self.game.piece_at(end_position);
let is_pawn_promoted =
initial_piece != final_piece && !initial_piece.is_crowned() && final_piece.is_crowned();
let logged_move = match mv {
Move::Movement { start, direction } => {
LoggedMove::movement(start, direction, is_pawn_promoted)
}
Move::Capture { start, direction } => {
let multi_capture = self.game.next_move == next_player_move;
LoggedMove::capture(
start,
direction,
is_pawn_promoted,
crowned_capture,
multi_capture,
)
}
};
self.moves.push_evict(logged_move);
Ok(()) Ok(())
} }
@@ -750,7 +802,7 @@ mod std {
#[cfg(feature = "std")] #[cfg(feature = "std")]
mod tests { mod tests {
extern crate std; extern crate std;
use crate::{Move, MoveDirection}; use crate::{LoggedMove, MoveDirection};
use super::{DraughtsBoard, Piece, Position}; use super::{DraughtsBoard, Piece, Position};
use std::collections::HashMap; use std::collections::HashMap;
@@ -774,10 +826,22 @@ mod tests {
}); });
let moves = [ let moves = [
Move::capture(Position::new(0, 4).unwrap(), MoveDirection::NW, false), LoggedMove::capture(
Move::movement(Position::new(0, 4).unwrap(), MoveDirection::NE), Position::new(0, 4).unwrap(),
Move::capture(Position::new(5, 5).unwrap(), MoveDirection::SW, false), MoveDirection::NW,
Move::movement(Position::new(4, 4).unwrap(), MoveDirection::NW), false,
false,
false,
),
LoggedMove::movement(Position::new(0, 4).unwrap(), MoveDirection::NE, false),
LoggedMove::capture(
Position::new(5, 5).unwrap(),
MoveDirection::SW,
false,
false,
false,
),
LoggedMove::movement(Position::new(4, 4).unwrap(), MoveDirection::NW, false),
]; ];
for mv in moves { for mv in moves {
let mut board_clone = board.clone(); let mut board_clone = board.clone();
@@ -875,7 +939,7 @@ mod ai_tests {
assert_eq!( assert_eq!(
Some(Move::movement( Some(Move::movement(
Position::new(6, 2).unwrap(), Position::new(6, 2).unwrap(),
MoveDirection::SW MoveDirection::SW,
)), )),
best_move best_move
); );

View File

@@ -9,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, MoveDirection}; pub use movement::{LoggedMove, 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;

View File

@@ -21,26 +21,75 @@ impl MoveDirection {
} }
} }
pub fn compute_end_position(
start_position: Position,
direction: MoveDirection,
is_capture: bool,
) -> Position {
if is_capture {
let direction = direction;
match direction {
MoveDirection::NE => start_position + (2, 2),
MoveDirection::SE => start_position + (-2, 2),
MoveDirection::SW => start_position + (-2, -2),
MoveDirection::NW => start_position + (2, -2),
}
} else {
match direction {
MoveDirection::NE => start_position + (1, 1),
MoveDirection::SE => start_position + (-1, 1),
MoveDirection::SW => start_position + (-1, -1),
MoveDirection::NW => start_position + (1, -1),
}
}
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Move { pub struct LoggedMove {
data: u16, data: u16,
} }
impl Move { impl LoggedMove {
pub fn movement(start: Position, dir: MoveDirection) -> Move { fn is_pawn_promoted(
let mut result = <Position as Into<u8>>::into(start) as u16; piece: Piece,
result |= (dir as u16) << 5; start: Position,
Move { data: result } dir: MoveDirection,
is_capture: bool,
) -> bool {
let end_row = compute_end_position(start, dir, is_capture).row();
!piece.is_crowned() && (Some(Player::White) == piece.player() && end_row == 7)
|| (Some(Player::Red) == piece.player() && end_row == 0)
} }
pub fn capture(start: Position, dir: MoveDirection, crowned_capture: bool) -> Move { pub fn movement(start: Position, dir: MoveDirection, is_pawn_promoted: bool) -> LoggedMove {
let mut result = <Position as Into<u8>>::into(start) as u16;
result |= (dir as u16) << 5;
if is_pawn_promoted {
result |= 1u16 << 8;
}
LoggedMove { data: result }
}
pub fn capture(
start: Position,
dir: MoveDirection,
is_pawn_promoted: bool,
crowned_capture: bool,
multi_capture: bool,
) -> LoggedMove {
let mut result = <Position as Into<u8>>::into(start) as u16; let mut result = <Position as Into<u8>>::into(start) as u16;
result |= (dir as u16) << 5; result |= (dir as u16) << 5;
result |= 1u16 << 7; result |= 1u16 << 7;
if crowned_capture { if is_pawn_promoted {
result |= 1u16 << 8; result |= 1u16 << 8;
} }
Move { data: result } if crowned_capture {
result |= 1u16 << 9;
}
if multi_capture {
result |= 1u16 << 10;
}
LoggedMove { data: result }
} }
pub fn start_position(&self) -> Position { pub fn start_position(&self) -> Position {
@@ -59,63 +108,173 @@ impl Move {
!self.is_capture() !self.is_capture()
} }
pub fn crowned_captured(&self) -> bool { pub fn pawn_promoted(&self) -> bool {
(self.data & 256) != 0 (self.data & 256) != 0
} }
pub fn crowned_captured(&self) -> bool {
(self.data & 512) != 0
}
pub fn multi_capture(&self) -> bool {
(self.data & 1024) != 0
}
pub fn get_end_position(&self) -> Position { pub fn get_end_position(&self) -> Position {
if self.is_capture() { compute_end_position(
let start = self.start_position(); self.start_position().clone(),
let direction = self.direction(); self.direction(),
match direction { self.is_capture(),
MoveDirection::NE => start + (2, 2), )
MoveDirection::SE => start + (-2, 2),
MoveDirection::SW => start + (-2, -2),
MoveDirection::NW => start + (2, -2),
} }
} else {
let start = self.start_position();
let direction = self.direction();
match direction {
MoveDirection::NE => start + (1, 1),
MoveDirection::SE => start + (-1, 1),
MoveDirection::SW => start + (-1, -1),
MoveDirection::NW => start + (1, -1),
} }
#[derive(Debug, PartialEq, Eq)]
pub enum Move {
Movement {
start: Position,
direction: MoveDirection,
},
Capture {
start: Position,
direction: MoveDirection,
},
}
impl Move {
pub fn movement(start: Position, direction: MoveDirection) -> Move {
Move::Movement { start, direction }
}
pub fn capture(start: Position, direction: MoveDirection) -> Move {
Move::Capture { start, direction }
}
pub fn is_movement(&self) -> bool {
match self {
Move::Movement {
start: _,
direction: _,
} => true,
Move::Capture {
start: _,
direction: _,
} => false,
}
}
pub fn is_capture(&self) -> bool {
!self.is_movement()
}
pub fn get_start_position(&self) -> Position {
match self {
Move::Movement {
start,
direction: _,
} => start.clone(),
Move::Capture {
start,
direction: _,
} => start.clone(),
}
}
pub fn get_end_position(&self) -> Position {
compute_end_position(
self.get_start_position(),
self.get_direction(),
self.is_capture(),
)
}
pub fn get_direction(&self) -> MoveDirection {
match self {
Move::Movement {
start: _,
direction,
} => direction.clone(),
Move::Capture {
start: _,
direction,
} => direction.clone(),
} }
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Move, MoveDirection}; use super::{LoggedMove, MoveDirection, Piece};
use crate::Position; use crate::Position;
struct MoveData { struct MoveData {
piece: Piece,
start: Position, start: Position,
dir: MoveDirection, dir: MoveDirection,
capture: bool, capture: bool,
crowned_capture: bool, crowned_capture: bool,
pawn_promoted: bool,
multi_capture: bool,
} }
#[test] #[test]
fn test_from_into() { fn test_from_into() {
let move_datas = [MoveData { let move_datas = [
MoveData {
piece: Piece::SimpleRedPawn,
start: Position::new(0, 0).unwrap(), start: Position::new(0, 0).unwrap(),
dir: super::MoveDirection::NE, dir: super::MoveDirection::NE,
capture: false, capture: false,
crowned_capture: false, crowned_capture: false,
}]; pawn_promoted: false,
multi_capture: false,
},
MoveData {
piece: Piece::SimpleWhitePawn,
start: Position::new(3, 5).unwrap(),
dir: super::MoveDirection::SW,
capture: false,
crowned_capture: false,
pawn_promoted: false,
multi_capture: false,
},
MoveData {
piece: Piece::SimpleWhitePawn,
start: Position::new(6, 6).unwrap(),
dir: super::MoveDirection::NW,
capture: false,
crowned_capture: false,
pawn_promoted: true,
multi_capture: false,
},
MoveData {
piece: Piece::SimpleWhitePawn,
start: Position::new(5, 5).unwrap(),
dir: super::MoveDirection::NE,
capture: true,
crowned_capture: true,
pawn_promoted: true,
multi_capture: true,
},
];
for md in move_datas { for md in move_datas {
let mv = if md.capture { let mv = if md.capture {
Move::capture(md.start, md.dir, md.crowned_capture) LoggedMove::capture(
md.start,
md.dir,
md.pawn_promoted,
md.crowned_capture,
md.multi_capture,
)
} else { } else {
Move::movement(md.start, md.dir) LoggedMove::movement(md.start, md.dir, md.pawn_promoted)
}; };
assert_eq!(md.start, mv.start_position()); assert_eq!(md.start, mv.start_position());
assert_eq!(md.dir, mv.direction()); assert_eq!(md.dir, mv.direction());
assert_eq!(md.capture, mv.is_capture()); assert_eq!(md.capture, mv.is_capture());
assert_eq!(md.crowned_capture, mv.crowned_captured()); assert_eq!(md.crowned_capture, mv.crowned_captured());
assert_eq!(md.pawn_promoted, mv.pawn_promoted());
assert_eq!(md.multi_capture, mv.multi_capture());
} }
} }
} }