fix frontend board mapping, game-over handling, and WASM-4
- fix Xform yx and add a shared view-aware board_position helper - add GTK automatic game-over, restart, status, and multi-capture selection - pin librsvg to the 2.60 series to match GTK4/glib 0.20 - add WASM orientation, undo/redo/new/flip controls, and winner overlay - replace WASM crown images with canvas paths and remove dead files/deps - make WASM-4 playable with mouse input, crowns, winner handling, and correct mapping - move the WASM-4 release profile to the workspace root - clean up frontend dead code and update the README
This commit is contained in:
1 parent
dd3b918561
commit
f17d328ed5
20 files changed
+997
-1290
No files matched your search
@@ -1,7 +1,5 @@
|
||||
use glib::ExitCode;
|
||||
use rdraught::{DraughtsBoard, DraughtsGame, Move, Piece, Player, Position};
|
||||
use rdraught_gtk::run;
|
||||
use std::collections::HashMap;
|
||||
use rdraught::{DraughtsGame, Move};
|
||||
use std::mem::size_of;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gtk4::{AlertDialog, Window, prelude::IsA};
|
||||
use rdraught::Player;
|
||||
|
||||
pub(crate) async fn create_dialog<W: IsA<Window>>(window: W, winner: Player) {
|
||||
pub(crate) fn create_dialog<W: IsA<Window>>(window: W, winner: Player) {
|
||||
let msg = match winner {
|
||||
Player::Red => String::from("Red player wins"),
|
||||
Player::White => String::from("White player wins"),
|
||||
|
||||
@@ -4,7 +4,7 @@ use rdraught::Player;
|
||||
use rdraught_ui_common::SharedMutable;
|
||||
|
||||
pub(crate) fn create(application: &Application, current_player: SharedMutable<Player>) -> Window {
|
||||
let label = Label::builder().label("Main player:").build();
|
||||
let label = Label::builder().label("Board orientation:").build();
|
||||
let red_player_button = CheckButton::builder().label("Red").active(true).build();
|
||||
let white_player_button = CheckButton::builder()
|
||||
.label("White")
|
||||
@@ -44,19 +44,11 @@ pub(crate) fn create(application: &Application, current_player: SharedMutable<Pl
|
||||
layout.set_margin_bottom(10);
|
||||
layout.set_margin_start(10);
|
||||
layout.set_margin_end(10);
|
||||
let player_select_dialog = Window::builder()
|
||||
Window::builder()
|
||||
.application(application)
|
||||
.title("Rdraught")
|
||||
.modal(true)
|
||||
.child(&layout)
|
||||
.visible(true)
|
||||
.build();
|
||||
|
||||
player_select_dialog.connect_close_request(move |window| {
|
||||
if let Some(application) = window.application() {
|
||||
application.remove_window(window);
|
||||
}
|
||||
glib::Propagation::Proceed
|
||||
});
|
||||
player_select_dialog
|
||||
.build()
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
use core::f64::consts::PI;
|
||||
use gdk4::cairo::{Context as CairoContext, Matrix, Rectangle};
|
||||
use glib::ExitCode;
|
||||
use gtk4::glib::{MainContext, Propagation};
|
||||
use gtk4::glib::Propagation;
|
||||
use gtk4::{self as gtk, gdk::ffi::GDK_BUTTON_PRIMARY};
|
||||
use gtk4::{Application, Button, DrawingArea, HeaderBar, prelude::*};
|
||||
use gtk4::{Application, Button, DrawingArea, HeaderBar, Label, prelude::*};
|
||||
use rdraught::{
|
||||
DraughtsBoard, DraughtsGame, Move, Piece, Player, Position, RDraughtApplication,
|
||||
RectangularBoard,
|
||||
@@ -14,7 +14,7 @@ const SQUARE_SIZE: f64 = 1.0;
|
||||
use super::final_dialog;
|
||||
use super::greeting_dialog;
|
||||
use rdraught_ui_common::{
|
||||
Point, SharedMutable, SharedMutableRef, new_shared_mut, new_shared_mut_ref,
|
||||
Point, SharedMutable, SharedMutableRef, board_position, new_shared_mut, new_shared_mut_ref,
|
||||
};
|
||||
|
||||
const CROWN_RED: &[u8] = include_bytes!("crown_red.svg");
|
||||
@@ -25,6 +25,47 @@ fn transform_point(p: &Point, m: &Matrix) -> Point {
|
||||
Point::new(x, y)
|
||||
}
|
||||
|
||||
fn player_name(player: Player) -> &'static str {
|
||||
match player {
|
||||
Player::Red => "Red",
|
||||
Player::White => "White",
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_game_state(
|
||||
rd: &SharedMutableRef<RDraughtApplication>,
|
||||
status: &Label,
|
||||
undo_button: &Button,
|
||||
redo_button: &Button,
|
||||
) -> Option<Player> {
|
||||
let rd = rd.borrow();
|
||||
let game = rd.game();
|
||||
let winner = game.winner();
|
||||
let text = match winner {
|
||||
Some(winner) => format!("{} wins", player_name(winner)),
|
||||
None => format!("{} to move", player_name(game.next_move())),
|
||||
};
|
||||
status.set_text(&text);
|
||||
undo_button.set_sensitive(winner.is_none() && rd.can_undo());
|
||||
redo_button.set_sensitive(winner.is_none() && rd.can_redo());
|
||||
winner
|
||||
}
|
||||
|
||||
fn handle_game_over(
|
||||
winner: Option<Player>,
|
||||
finished: &SharedMutable<bool>,
|
||||
drawing_area: &DrawingArea,
|
||||
window: >k::ApplicationWindow,
|
||||
) {
|
||||
if let Some(winner) = winner {
|
||||
if !finished.get() {
|
||||
finished.set(true);
|
||||
drawing_area.set_sensitive(false);
|
||||
final_dialog::create_dialog(window.clone(), winner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait AugmentedRect {
|
||||
fn from_points(tl: Point, br: Point) -> Rectangle;
|
||||
fn center(&self) -> Point;
|
||||
@@ -253,6 +294,10 @@ fn create_game_window(
|
||||
redo_button.set_icon_name("edit-redo-symbolic");
|
||||
redo_button.set_sensitive(false);
|
||||
|
||||
let finished: SharedMutable<bool> = new_shared_mut(false);
|
||||
let status = Label::new(None);
|
||||
let new_game_button = Button::with_label("New Game");
|
||||
|
||||
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.
|
||||
@@ -261,6 +306,11 @@ fn create_game_window(
|
||||
window.set_child(Some(&drawing_area));
|
||||
|
||||
header_bar.pack_start(&undo_button);
|
||||
header_bar.pack_start(&redo_button);
|
||||
header_bar.pack_start(&new_game_button);
|
||||
header_bar.pack_end(&status);
|
||||
let _ = refresh_game_state(&rd, &status, &undo_button, &redo_button);
|
||||
|
||||
{
|
||||
let da = drawing_area.clone();
|
||||
let rd = rd.clone();
|
||||
@@ -268,19 +318,23 @@ fn create_game_window(
|
||||
let redo_btn_clone = redo_button.clone();
|
||||
let selected_piece = selected_piece.clone();
|
||||
let available_moves = available_moves.clone();
|
||||
let status = status.clone();
|
||||
let finished = finished.clone();
|
||||
let window = window.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();
|
||||
{
|
||||
let mut app = rd.borrow_mut();
|
||||
if app.can_undo() {
|
||||
app.undo().ok();
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
}
|
||||
}
|
||||
let winner = refresh_game_state(&rd, &status, &undo_btn_clone, &redo_btn_clone);
|
||||
handle_game_over(winner, &finished, &da, &window);
|
||||
da.queue_draw();
|
||||
});
|
||||
}
|
||||
header_bar.pack_start(&redo_button);
|
||||
{
|
||||
let da = drawing_area.clone();
|
||||
let rd = rd.clone();
|
||||
@@ -288,16 +342,40 @@ fn create_game_window(
|
||||
let redo_btn_clone = redo_button.clone();
|
||||
let selected_piece = selected_piece.clone();
|
||||
let available_moves = available_moves.clone();
|
||||
let status = status.clone();
|
||||
let finished = finished.clone();
|
||||
let window = window.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();
|
||||
{
|
||||
let mut app = rd.borrow_mut();
|
||||
if app.can_redo() {
|
||||
app.redo().ok();
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
}
|
||||
}
|
||||
let winner = refresh_game_state(&rd, &status, &undo_btn_clone, &redo_btn_clone);
|
||||
handle_game_over(winner, &finished, &da, &window);
|
||||
da.queue_draw();
|
||||
});
|
||||
}
|
||||
{
|
||||
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();
|
||||
let status = status.clone();
|
||||
let finished = finished.clone();
|
||||
new_game_button.connect_clicked(move |_| {
|
||||
*rd.borrow_mut() = RDraughtApplication::new(DraughtsGame::default());
|
||||
finished.set(false);
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
da.set_sensitive(true);
|
||||
let _ = refresh_game_state(&rd, &status, &undo_btn_clone, &redo_btn_clone);
|
||||
da.queue_draw();
|
||||
});
|
||||
}
|
||||
// Get the allocation information for the widget.
|
||||
@@ -362,10 +440,7 @@ fn create_game_window(
|
||||
// Loop over rows and columns to draw each chessboard cell.
|
||||
for row in 0..DraughtsBoard::rows() {
|
||||
for col in 0..DraughtsBoard::columns() {
|
||||
let position = match current_player {
|
||||
Player::Red => Position::new(row as u8, (8 - 1 - col) as u8),
|
||||
Player::White => Position::new((8 - 1 - row) as u8, col as u8),
|
||||
};
|
||||
let position = board_position(current_player, row as u8, col as u8);
|
||||
let square = Rectangle::new(
|
||||
col as f64 * SQUARE_SIZE,
|
||||
row as f64 * SQUARE_SIZE,
|
||||
@@ -481,99 +556,74 @@ fn create_game_window(
|
||||
let window = window.clone();
|
||||
let undo_btn_clone = undo_button.clone();
|
||||
let redo_btn_clone = redo_button.clone();
|
||||
let selected_piece = selected_piece.clone();
|
||||
let finished = finished.clone();
|
||||
let status = status.clone();
|
||||
gesture.connect_pressed(move |gesture, _, x, y| {
|
||||
gesture.set_state(gtk::EventSequenceState::Claimed);
|
||||
if let Some(winner) = rd.borrow().game().winner() {
|
||||
MainContext::default()
|
||||
.spawn_local(final_dialog::create_dialog(window.clone(), winner));
|
||||
} else {
|
||||
let xform = xform.borrow();
|
||||
let inverse = {
|
||||
let mut m = *xform;
|
||||
m.invert();
|
||||
m
|
||||
};
|
||||
let p = transform_point(&Point::new(x, y), &inverse);
|
||||
if board_clone.contains(&p) {
|
||||
let p = p - board_clone.tl();
|
||||
// println!("Point: {:?}", p);
|
||||
let position = match current_player {
|
||||
Player::White => Position::new(
|
||||
(8.0 - (p.y() / SQUARE_SIZE)) as u8,
|
||||
(p.x() / SQUARE_SIZE) as u8,
|
||||
),
|
||||
Player::Red => Position::new(
|
||||
(p.y() / SQUARE_SIZE) as u8,
|
||||
(8.0 - p.x() / SQUARE_SIZE) as u8,
|
||||
),
|
||||
};
|
||||
// println!("Selected position: {:?}", position);
|
||||
let piece = {
|
||||
let rd = rd.borrow();
|
||||
let draughts_game = rd.game();
|
||||
position
|
||||
.clone()
|
||||
.map_or(Piece::NoPiece, |it| draughts_game.piece_at(it))
|
||||
// println!("Selected piece: {:?}", piece);
|
||||
};
|
||||
let am = available_moves.replace(Vec::new());
|
||||
let mut move_applied = false;
|
||||
if !am.is_empty() {
|
||||
if let Ok(pos) = position {
|
||||
for mv in am.into_iter() {
|
||||
if mv.get_end_position() == Some(pos) {
|
||||
let mut rd_app = rd.borrow_mut();
|
||||
// println!("Applied move: {:?}", mv);
|
||||
if rd_app.apply_move(mv).is_err() {
|
||||
continue;
|
||||
}
|
||||
// let game_copy = rd_app.game().clone();
|
||||
// thread::spawn(move || {
|
||||
// if let (Some(mv), analyzed_moves) =
|
||||
// game_copy.get_best_move(10)
|
||||
// {
|
||||
// println!(
|
||||
// "Next best move: {:?}, analyzed moves: {}",
|
||||
// mv, analyzed_moves
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
move_applied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 finished.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
let xform = xform.borrow();
|
||||
let inverse = {
|
||||
let mut m = *xform;
|
||||
m.invert();
|
||||
m
|
||||
};
|
||||
let p = transform_point(&Point::new(x, y), &inverse);
|
||||
if !board_clone.contains(&p) {
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
drawing_area.queue_draw();
|
||||
return;
|
||||
}
|
||||
|
||||
let p = p - board_clone.tl();
|
||||
let screen_row = (p.y() / SQUARE_SIZE) as u8;
|
||||
let screen_col = (p.x() / SQUARE_SIZE) as u8;
|
||||
let position = board_position(current_player, screen_row, screen_col).ok();
|
||||
let am = available_moves.replace(Vec::new());
|
||||
let mut move_applied = false;
|
||||
if let Some(pos) = position {
|
||||
for mv in am {
|
||||
if mv.get_end_position() == Some(pos) {
|
||||
let mut rd_app = rd.borrow_mut();
|
||||
if rd_app.apply_move(mv).is_err() {
|
||||
continue;
|
||||
}
|
||||
move_applied = true;
|
||||
break;
|
||||
}
|
||||
if !move_applied {
|
||||
let position = position.ok();
|
||||
match piece.player() {
|
||||
Some(Player::Red) => selected_piece.set(position),
|
||||
Some(Player::White) => selected_piece.set(position),
|
||||
None => selected_piece.set(None),
|
||||
}
|
||||
if piece.player().is_none() {
|
||||
selected_piece.set(None)
|
||||
} else {
|
||||
let mut am = available_moves.borrow_mut();
|
||||
selected_piece.set(position);
|
||||
if let Some(position) = position {
|
||||
for mv in rd.borrow().game().moves_for_piece(position) {
|
||||
am.push(mv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if move_applied {
|
||||
if let Some(pending) = rd.borrow().game().pending_capture() {
|
||||
selected_piece.set(Some(pending));
|
||||
let mut moves = available_moves.borrow_mut();
|
||||
moves.clear();
|
||||
for mv in rd.borrow().game().moves_for_piece(pending) {
|
||||
moves.push(mv);
|
||||
}
|
||||
} else {
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
}
|
||||
let winner = refresh_game_state(&rd, &status, &undo_btn_clone, &redo_btn_clone);
|
||||
handle_game_over(winner, &finished, &drawing_area, &window);
|
||||
} else {
|
||||
let mut moves = available_moves.borrow_mut();
|
||||
moves.clear();
|
||||
selected_piece.set(None);
|
||||
if let Some(position) = position {
|
||||
for mv in rd.borrow().game().moves_for_piece(position) {
|
||||
selected_piece.set(Some(position));
|
||||
moves.push(mv);
|
||||
}
|
||||
}
|
||||
drawing_area.queue_draw();
|
||||
}
|
||||
drawing_area.queue_draw();
|
||||
});
|
||||
}
|
||||
// Assign the gesture to the treeview
|
||||
@@ -582,11 +632,6 @@ fn create_game_window(
|
||||
}
|
||||
|
||||
fn on_activate(application: &Application, game: DraughtsGame) {
|
||||
// Initialize GTK before using any GTK functions.
|
||||
if gtk::init().is_err() {
|
||||
panic!("Failed to initialize GTK.");
|
||||
}
|
||||
|
||||
let current_player = new_shared_mut(Player::Red);
|
||||
let dialog = greeting_dialog::create(application, current_player.clone());
|
||||
let application = application.clone();
|
||||
|
||||
Reference in new issue
Block a user