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]
members = ["rdraught", "rdraught-cli", "rdraught-ui", "rdraught-w4"]
members = ["rdraught", "rdraught-cli", "rdraught-gtk", "rdraught-w4"]
resolver = "3"
[workspace.package]

View File

@@ -1,5 +1,5 @@
[package]
name = "rdraught-ui"
name = "rdraught-gtk"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
@@ -18,10 +18,10 @@ gio.workspace = true
glib.workspace = true
[lib]
name = "rdraught_ui"
name = "rdraught_gtk"
crate-type = ["lib"]
bench = false
[[bin]]
name = "rdraught-ui"
name = "rdraught-gtk"
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 {
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::constants::{BITS_PER_POSITION, POSITIONS, POSITIONS_PER_ROW};
use super::movement::{Move, MoveDirection};
use super::movement::{LoggedMove, Move, MoveDirection};
use super::position::Position;
use crate::piece::Piece;
use crate::player::Player;
@@ -89,7 +89,7 @@ impl DraughtsBoard {
}
fn check_move_valid(&self, mv: &Move) -> Result<(), Error> {
let start = mv.start_position();
let start = mv.get_start_position();
if mv.is_movement() {
let mut move_is_possible = 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 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(mv.start_position(), p);
self.set(mv.start_position(), initial_piece);
if mv.is_capture() {
let captured_position = (mv.start_position() + mv.get_end_position()) / (2, 2);
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 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() {
let end = mv.get_end_position();
self.set(start, Piece::NoPiece);
self.set(end, piece);
self.set(end, final_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(end, final_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.get_start_position();
let piece = self.get_piece(&start);
if let Piece::NoPiece = piece {
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.next_turn();
if !mv.is_capture() || !mv.multi_capture() {
self.next_turn();
}
}
fn redo_move(&mut self, mv: &Move) {
fn redo_move(&mut self, mv: &LoggedMove) {
self.board.apply_move(mv);
self.next_turn();
if !mv.is_capture() || !mv.multi_capture() {
self.next_turn();
}
}
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);
if let Some(player) = piece.player() {
if mv.is_movement() {
@@ -631,11 +655,7 @@ impl<'a> Iterator for MoveIterator<'a> {
}
_ => {
if piece.player() != piece_at_next_position.player() {
let capture = Move::capture(
self.position,
direction,
piece_at_next_position.is_crowned(),
);
let capture = Move::capture(self.position, direction);
let next_position = capture.get_end_position();
if DraughtsBoard::is_position_valid(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 {
game: DraughtsGame,
moves: CircularBuffer<Move, 128>,
moves: CircularBuffer<LoggedMove, 128>,
cursor: usize,
}
@@ -662,7 +682,7 @@ impl RDraughtApplication {
pub fn new(game: DraughtsGame) -> RDraughtApplication {
RDraughtApplication {
game,
moves: CircularBuffer::<Move, 128>::new(),
moves: CircularBuffer::<LoggedMove, 128>::new(),
cursor: 0usize,
}
}
@@ -722,8 +742,40 @@ impl RDraughtApplication {
self.moves.pop_back();
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.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(())
}
@@ -750,7 +802,7 @@ mod std {
#[cfg(feature = "std")]
mod tests {
extern crate std;
use crate::{Move, MoveDirection};
use crate::{LoggedMove, MoveDirection};
use super::{DraughtsBoard, Piece, Position};
use std::collections::HashMap;
@@ -774,10 +826,22 @@ mod tests {
});
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),
LoggedMove::capture(
Position::new(0, 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 {
let mut board_clone = board.clone();
@@ -875,7 +939,7 @@ mod ai_tests {
assert_eq!(
Some(Move::movement(
Position::new(6, 2).unwrap(),
MoveDirection::SW
MoveDirection::SW,
)),
best_move
);

View File

@@ -9,7 +9,7 @@ mod player;
mod position;
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 player::Player;
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)]
pub struct Move {
pub struct LoggedMove {
data: u16,
}
impl Move {
pub fn movement(start: Position, dir: MoveDirection) -> Move {
let mut result = <Position as Into<u8>>::into(start) as u16;
result |= (dir as u16) << 5;
Move { data: result }
impl LoggedMove {
fn is_pawn_promoted(
piece: Piece,
start: Position,
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;
result |= (dir as u16) << 5;
result |= 1u16 << 7;
if crowned_capture {
if is_pawn_promoted {
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 {
@@ -59,63 +108,173 @@ impl Move {
!self.is_capture()
}
pub fn crowned_captured(&self) -> bool {
pub fn pawn_promoted(&self) -> bool {
(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 {
if self.is_capture() {
let start = self.start_position();
let direction = self.direction();
match direction {
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),
}
compute_end_position(
self.start_position().clone(),
self.direction(),
self.is_capture(),
)
}
}
#[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)]
mod tests {
use super::{Move, MoveDirection};
use super::{LoggedMove, MoveDirection, Piece};
use crate::Position;
struct MoveData {
piece: Piece,
start: Position,
dir: MoveDirection,
capture: bool,
crowned_capture: bool,
pawn_promoted: bool,
multi_capture: bool,
}
#[test]
fn test_from_into() {
let move_datas = [MoveData {
start: Position::new(0, 0).unwrap(),
dir: super::MoveDirection::NE,
capture: false,
crowned_capture: false,
}];
let move_datas = [
MoveData {
piece: Piece::SimpleRedPawn,
start: Position::new(0, 0).unwrap(),
dir: super::MoveDirection::NE,
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 {
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 {
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.dir, mv.direction());
assert_eq!(md.capture, mv.is_capture());
assert_eq!(md.crowned_capture, mv.crowned_captured());
assert_eq!(md.pawn_promoted, mv.pawn_promoted());
assert_eq!(md.multi_capture, mv.multi_capture());
}
}
}