From 553d3f8496ea5476b689a4b1016958eb2f5d349b Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 5 Apr 2023 09:13:50 -0400 Subject: [PATCH 1/7] Rename Goban to Board and create the IntersectionElement --- kifu/kifu-core/src/ui/mod.rs | 5 +- kifu/kifu-core/src/ui/playing_field.rs | 4 +- kifu/kifu-core/src/ui/types.rs | 48 ++++++++++------ kifu/kifu-gtk/src/ui/{goban.rs => board.rs} | 64 +++++++++++---------- kifu/kifu-gtk/src/ui/mod.rs | 4 +- kifu/kifu-gtk/src/ui/playing_field.rs | 14 ++--- 6 files changed, 81 insertions(+), 58 deletions(-) rename kifu/kifu-gtk/src/ui/{goban.rs => board.rs} (81%) diff --git a/kifu/kifu-core/src/ui/mod.rs b/kifu/kifu-core/src/ui/mod.rs index e7b40c0..abe5612 100644 --- a/kifu/kifu-core/src/ui/mod.rs +++ b/kifu/kifu-core/src/ui/mod.rs @@ -2,4 +2,7 @@ mod playing_field; pub use playing_field::{playing_field, PlayingFieldView}; mod types; -pub use types::{ChatElement, GobanElement, PlayerCardElement, StoneElement, TextFieldElement}; +pub use types::{ + BoardElement, ChatElement, IntersectionElement, PlayerCardElement, StoneElement, + TextFieldElement, +}; diff --git a/kifu/kifu-core/src/ui/playing_field.rs b/kifu/kifu-core/src/ui/playing_field.rs index 8787338..70cc951 100644 --- a/kifu/kifu-core/src/ui/playing_field.rs +++ b/kifu/kifu-core/src/ui/playing_field.rs @@ -3,7 +3,7 @@ use crate::ui::types; #[derive(Clone, Debug)] pub struct PlayingFieldView { - pub board: types::GobanElement, + pub board: types::BoardElement, pub player_card_black: types::PlayerCardElement, pub player_card_white: types::PlayerCardElement, pub chat: types::ChatElement, @@ -12,7 +12,7 @@ pub struct PlayingFieldView { } pub fn playing_field() -> PlayingFieldView { - let board = types::GobanElement::default(); + let board = types::BoardElement::default(); let player_card_black = types::PlayerCardElement { color: Color::Black, diff --git a/kifu/kifu-core/src/ui/types.rs b/kifu/kifu-core/src/ui/types.rs index 05092f3..98bbaf1 100644 --- a/kifu/kifu-core/src/ui/types.rs +++ b/kifu/kifu-core/src/ui/types.rs @@ -1,12 +1,12 @@ use crate::types::{Color, Size}; -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct Jitter { pub x: i8, pub y: i8, } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct StoneElement { pub color: Color, pub jitter: Jitter, @@ -21,28 +21,44 @@ impl StoneElement { } } -#[derive(Clone, Debug)] -pub struct GobanElement { - pub size: Size, - pub spaces: Vec>, +#[derive(Clone, Copy, Debug)] +pub struct PlayStoneRequest { + col: u8, + row: u8, } -impl GobanElement { +#[derive(Clone, Copy, Debug)] +pub enum IntersectionElement { + Unplayable, + Empty(PlayStoneRequest), + Filled(StoneElement), +} + +#[derive(Clone, Debug)] +pub struct BoardElement { + pub size: Size, + pub spaces: Vec, +} + +impl BoardElement { pub fn new(size: Size) -> Self { - Self { - size: size.clone(), - spaces: (0..((size.width as usize) * (size.height as usize))) - .map(|_| None) - .collect::>>(), - } + let spaces: Vec = (0..size.height) + .map(|row| { + (0..size.width) + .map(|col| IntersectionElement::Empty(PlayStoneRequest { col, row })) + .collect::>() + }) + .collect::>>() + .concat(); + Self { size, spaces } } - pub fn stone_mut<'a>(&'a mut self, row: u8, col: u8) -> &'a mut Option { + pub fn stone_mut<'a>(&'a mut self, row: u8, col: u8) -> &'a mut IntersectionElement { let addr = self.addr(row, col); &mut self.spaces[addr] } - pub fn stone(&self, row: u8, col: u8) -> Option { + pub fn stone(&self, row: u8, col: u8) -> IntersectionElement { self.spaces[self.addr(row, col)].clone() } @@ -51,7 +67,7 @@ impl GobanElement { } } -impl Default for GobanElement { +impl Default for BoardElement { fn default() -> Self { Self::new(Size::default()) } diff --git a/kifu/kifu-gtk/src/ui/goban.rs b/kifu/kifu-gtk/src/ui/board.rs similarity index 81% rename from kifu/kifu-gtk/src/ui/goban.rs rename to kifu/kifu-gtk/src/ui/board.rs index 2580500..20ae649 100644 --- a/kifu/kifu-gtk/src/ui/goban.rs +++ b/kifu/kifu-gtk/src/ui/board.rs @@ -6,7 +6,10 @@ use gtk::{ subclass::prelude::*, }; use image::io::Reader as ImageReader; -use kifu_core::{ui::GobanElement, Color}; +use kifu_core::{ + ui::{BoardElement, IntersectionElement}, + Color, +}; use std::{cell::RefCell, io::Cursor, rc::Rc}; const WIDTH: i32 = 800; @@ -18,36 +21,36 @@ struct Addr { column: u8, } -pub struct GobanPrivate { +pub struct BoardPrivate { drawing_area: gtk::DrawingArea, current_player: Rc>, - goban: Rc>, + board: Rc>, cursor_location: Rc>, } #[glib::object_subclass] -impl ObjectSubclass for GobanPrivate { - const NAME: &'static str = "Goban"; - type Type = Goban; +impl ObjectSubclass for BoardPrivate { + const NAME: &'static str = "Board"; + type Type = Board; type ParentType = gtk::Grid; - fn new() -> GobanPrivate { - GobanPrivate { + fn new() -> BoardPrivate { + BoardPrivate { drawing_area: Default::default(), current_player: Rc::new(RefCell::new(Color::Black)), - goban: Default::default(), + board: Default::default(), cursor_location: Default::default(), } } } -impl ObjectImpl for GobanPrivate { +impl ObjectImpl for BoardPrivate { fn constructed(&self) { self.drawing_area.set_width_request(WIDTH); self.drawing_area.set_height_request(HEIGHT); - let goban = self.goban.clone(); + let board = self.board.clone(); let cursor_location = self.cursor_location.clone(); let current_player = self.current_player.clone(); @@ -75,7 +78,7 @@ impl ObjectImpl for GobanPrivate { self.drawing_area .set_draw_func(move |_, context, width, height| { - let goban = goban.borrow(); + let board = board.borrow(); match background { Ok(Some(ref background)) => { context.set_source_pixbuf(&background, 0., 0.); @@ -87,8 +90,8 @@ impl ObjectImpl for GobanPrivate { context.set_source_rgb(0.1, 0.1, 0.1); context.set_line_width(2.); - let hspace_between = ((width - 40) as f64) / ((goban.size.width - 1) as f64); - let vspace_between = ((height - 40) as f64) / ((goban.size.height - 1) as f64); + let hspace_between = ((width - 40) as f64) / ((board.size.width - 1) as f64); + let vspace_between = ((height - 40) as f64) / ((board.size.height - 1) as f64); let pen = Pen { x_offset: 20., @@ -97,12 +100,12 @@ impl ObjectImpl for GobanPrivate { vspace_between, }; - (0..goban.size.width).for_each(|col| { + (0..board.size.width).for_each(|col| { context.move_to(20.0 + (col as f64) * hspace_between, 20.0); context.line_to(20.0 + (col as f64) * hspace_between, (height as f64) - 20.0); let _ = context.stroke(); }); - (0..goban.size.height).for_each(|row| { + (0..board.size.height).for_each(|row| { context.move_to(20.0, 20.0 + (row as f64) * vspace_between); context.line_to((width - 20) as f64, 20.0 + (row as f64) * vspace_between); let _ = context.stroke(); @@ -117,10 +120,11 @@ impl ObjectImpl for GobanPrivate { (0..19).for_each(|col| { (0..19).for_each(|row| { - match goban.stone(row, col) { - None => {} - Some(element) => { - pen.stone(&context, row, col, element.color); + match board.stone(row, col) { + IntersectionElement::Unplayable => {} + IntersectionElement::Empty(request) => {} + IntersectionElement::Filled(stone) => { + pen.stone(&context, row, col, stone.color); } }; }) @@ -132,14 +136,14 @@ impl ObjectImpl for GobanPrivate { let motion_controller = gtk::EventControllerMotion::new(); { - let goban = self.goban.clone(); + let board = self.board.clone(); let cursor = self.cursor_location.clone(); let drawing_area = self.drawing_area.clone(); motion_controller.connect_motion(move |_, x, y| { - let goban = goban.borrow(); + let board = board.borrow(); let mut cursor = cursor.borrow_mut(); - let hspace_between = ((WIDTH - 40) as f64) / ((goban.size.width - 1) as f64); - let vspace_between = ((HEIGHT - 40) as f64) / ((goban.size.height - 1) as f64); + let hspace_between = ((WIDTH - 40) as f64) / ((board.size.width - 1) as f64); + let vspace_between = ((HEIGHT - 40) as f64) / ((board.size.height - 1) as f64); let addr = Addr { column: ((x.round() - 20.) / hspace_between).round() as u8, @@ -156,22 +160,22 @@ impl ObjectImpl for GobanPrivate { self.drawing_area.add_controller(motion_controller); } } -impl WidgetImpl for GobanPrivate {} -impl GridImpl for GobanPrivate {} +impl WidgetImpl for BoardPrivate {} +impl GridImpl for BoardPrivate {} glib::wrapper! { - pub struct Goban(ObjectSubclass) @extends gtk::Grid, gtk::Widget; + pub struct Board(ObjectSubclass) @extends gtk::Grid, gtk::Widget; } -impl Goban { +impl Board { pub fn new() -> Self { let s: Self = Object::builder().build(); s.attach(&s.imp().drawing_area, 1, 1, 1, 1); s } - pub fn set_board(&self, goban: GobanElement) { - *self.imp().goban.borrow_mut() = goban; + pub fn set_board(&self, board: BoardElement) { + *self.imp().board.borrow_mut() = board; self.imp().drawing_area.queue_draw(); } diff --git a/kifu/kifu-gtk/src/ui/mod.rs b/kifu/kifu-gtk/src/ui/mod.rs index 49630c7..6a469b3 100644 --- a/kifu/kifu-gtk/src/ui/mod.rs +++ b/kifu/kifu-gtk/src/ui/mod.rs @@ -7,8 +7,8 @@ pub use chat::Chat; mod playing_field; pub use playing_field::PlayingField; -mod goban; -pub use goban::Goban; +mod board; +pub use board::Board; #[cfg(feature = "screenplay")] pub use playing_field::playing_field_view; diff --git a/kifu/kifu-gtk/src/ui/playing_field.rs b/kifu/kifu-gtk/src/ui/playing_field.rs index 7ca1589..742754c 100644 --- a/kifu/kifu-gtk/src/ui/playing_field.rs +++ b/kifu/kifu-gtk/src/ui/playing_field.rs @@ -1,10 +1,10 @@ -use crate::ui::{Chat, Goban, PlayerCard}; +use crate::ui::{Board, Chat, PlayerCard}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use kifu_core::{ ui::{ - ChatElement, GobanElement, PlayerCardElement, PlayingFieldView, StoneElement, - TextFieldElement, + BoardElement, ChatElement, IntersectionElement, PlayerCardElement, PlayingFieldView, + StoneElement, TextFieldElement, }, Color, Size, }; @@ -23,7 +23,7 @@ pub struct PlayingFieldView { */ pub struct PlayingFieldPrivate { - goban: Goban, + goban: Board, player_card_white: Rc>>, player_card_black: Rc>>, chat: Rc>>, @@ -32,7 +32,7 @@ pub struct PlayingFieldPrivate { impl Default for PlayingFieldPrivate { fn default() -> Self { Self { - goban: Goban::new(), + goban: Board::new(), player_card_white: Rc::new(RefCell::new(None)), player_card_black: Rc::new(RefCell::new(None)), chat: Rc::new(RefCell::new(None)), @@ -48,7 +48,7 @@ impl ObjectSubclass for PlayingFieldPrivate { fn new() -> Self { Self { - goban: Goban::new(), + goban: Board::new(), player_card_white: Rc::new(RefCell::new(None)), player_card_black: Rc::new(RefCell::new(None)), chat: Rc::new(RefCell::new(None)), @@ -90,7 +90,7 @@ impl PlayingField { #[cfg(feature = "screenplay")] pub fn playing_field_view() -> PlayingFieldView { - let mut board = GobanElement::default(); + let mut board = Board::default(); *board.stone_mut(4, 4) = Some(StoneElement::new(Color::White)); *board.stone_mut(15, 15) = Some(StoneElement::new(Color::Black)); let player_card_black = PlayerCardElement { -- 2.44.1 From b64eda7efd9b5d2278b9211fc95475c680a4028d Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 5 Apr 2023 09:25:08 -0400 Subject: [PATCH 2/7] Don't render ghost stones on unplayable or filled spaces --- kifu/kifu-core/src/types.rs | 2 +- kifu/kifu-core/src/ui/types.rs | 8 ++++---- kifu/kifu-gtk/src/ui/board.rs | 10 +++++++++- kifu/kifu-gtk/src/ui/playing_field.rs | 10 +++++++--- rust-toolchain.toml | 3 --- 5 files changed, 21 insertions(+), 12 deletions(-) delete mode 100644 rust-toolchain.toml diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index e37992a..9b4eea9 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -1,6 +1,6 @@ use std::time::Duration; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Color { Black, White, diff --git a/kifu/kifu-core/src/ui/types.rs b/kifu/kifu-core/src/ui/types.rs index 98bbaf1..f607356 100644 --- a/kifu/kifu-core/src/ui/types.rs +++ b/kifu/kifu-core/src/ui/types.rs @@ -1,12 +1,12 @@ use crate::types::{Color, Size}; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Jitter { pub x: i8, pub y: i8, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct StoneElement { pub color: Color, pub jitter: Jitter, @@ -21,13 +21,13 @@ impl StoneElement { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct PlayStoneRequest { col: u8, row: u8, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum IntersectionElement { Unplayable, Empty(PlayStoneRequest), diff --git a/kifu/kifu-gtk/src/ui/board.rs b/kifu/kifu-gtk/src/ui/board.rs index 20ae649..58ce1b1 100644 --- a/kifu/kifu-gtk/src/ui/board.rs +++ b/kifu/kifu-gtk/src/ui/board.rs @@ -131,7 +131,15 @@ impl ObjectImpl for BoardPrivate { }); let cursor = cursor_location.borrow(); - pen.ghost_stone(context, cursor.row, cursor.column, *current_player.borrow()); + match board.stone(cursor.row, cursor.column) { + IntersectionElement::Empty(_) => pen.ghost_stone( + context, + cursor.row, + cursor.column, + *current_player.borrow(), + ), + _ => {} + } }); let motion_controller = gtk::EventControllerMotion::new(); diff --git a/kifu/kifu-gtk/src/ui/playing_field.rs b/kifu/kifu-gtk/src/ui/playing_field.rs index 742754c..ac6708b 100644 --- a/kifu/kifu-gtk/src/ui/playing_field.rs +++ b/kifu/kifu-gtk/src/ui/playing_field.rs @@ -90,9 +90,13 @@ impl PlayingField { #[cfg(feature = "screenplay")] pub fn playing_field_view() -> PlayingFieldView { - let mut board = Board::default(); - *board.stone_mut(4, 4) = Some(StoneElement::new(Color::White)); - *board.stone_mut(15, 15) = Some(StoneElement::new(Color::Black)); + let mut board = BoardElement::new(Size { + width: 19, + height: 19, + }); + *board.stone_mut(4, 4) = IntersectionElement::Filled(StoneElement::new(Color::White)); + *board.stone_mut(15, 15) = IntersectionElement::Filled(StoneElement::new(Color::Black)); + *board.stone_mut(18, 18) = IntersectionElement::Unplayable; let player_card_black = PlayerCardElement { color: Color::Black, name: "Savanni".to_owned(), diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 1cae25b..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,3 +0,0 @@ -[toolchain] -channel = "1.68.2" -components = ["rust-src"] -- 2.44.1 From 481226e8c6b5c67db0878619fd6d3904341382b5 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 5 Apr 2023 09:48:33 -0400 Subject: [PATCH 3/7] Detect mouse clicks on the board --- kifu/kifu-gtk/src/ui/board.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/kifu/kifu-gtk/src/ui/board.rs b/kifu/kifu-gtk/src/ui/board.rs index 58ce1b1..32efb75 100644 --- a/kifu/kifu-gtk/src/ui/board.rs +++ b/kifu/kifu-gtk/src/ui/board.rs @@ -165,7 +165,24 @@ impl ObjectImpl for BoardPrivate { }); } + let gesture = gtk::GestureClick::new(); + { + let board = self.board.clone(); + let cursor = self.cursor_location.clone(); + gesture.connect_released(move |_, _, _, _| { + let board = board.borrow(); + let cursor = cursor.borrow(); + match board.stone(cursor.row, cursor.column) { + IntersectionElement::Empty(request) => { + println!("need to send request: {:?}", request) + } + _ => {} + } + }); + } + self.drawing_area.add_controller(motion_controller); + self.drawing_area.add_controller(gesture); } } impl WidgetImpl for BoardPrivate {} -- 2.44.1 From 8df649d6cd6ba8bc07dd16a1e4ee786e60b52887 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 6 Apr 2023 21:52:39 -0400 Subject: [PATCH 4/7] Place stones on the board --- kifu/kifu-core/src/api.rs | 23 +++++- kifu/kifu-core/src/types.rs | 98 +++++++++++++++++++++++--- kifu/kifu-core/src/ui/playing_field.rs | 8 +-- kifu/kifu-core/src/ui/types.rs | 54 ++++++++++---- kifu/kifu-gtk/src/lib.rs | 20 ++++++ kifu/kifu-gtk/src/main.rs | 36 +++------- kifu/kifu-gtk/src/ui/board.rs | 16 ++++- kifu/kifu-gtk/src/ui/playing_field.rs | 31 ++++---- 8 files changed, 211 insertions(+), 75 deletions(-) diff --git a/kifu/kifu-core/src/api.rs b/kifu/kifu-core/src/api.rs index eb90866..983a5bc 100644 --- a/kifu/kifu-core/src/api.rs +++ b/kifu/kifu-core/src/api.rs @@ -2,11 +2,19 @@ use crate::types::AppState; use crate::ui::{playing_field, PlayingFieldView}; use std::sync::{Arc, RwLock}; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Request { PlayingField, + PlayStoneRequest(PlayStoneRequest), } -#[derive(Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PlayStoneRequest { + pub column: u8, + pub row: u8, +} + +#[derive(Clone, Debug)] pub enum Response { PlayingFieldView(PlayingFieldView), } @@ -25,7 +33,18 @@ impl CoreApp { pub async fn dispatch(&self, request: Request) -> Response { match request { - Request::PlayingField => Response::PlayingFieldView(playing_field()), + Request::PlayingField => { + let app_state = self.state.read().unwrap(); + let game = app_state.game.as_ref().unwrap(); + Response::PlayingFieldView(playing_field(game)) + } + Request::PlayStoneRequest(request) => { + let mut app_state = self.state.write().unwrap(); + app_state.place_stone(request); + + let game = app_state.game.as_ref().unwrap(); + Response::PlayingFieldView(playing_field(game)) + } } } diff --git a/kifu/kifu-core/src/types.rs b/kifu/kifu-core/src/types.rs index 9b4eea9..e9c9f32 100644 --- a/kifu/kifu-core/src/types.rs +++ b/kifu/kifu-core/src/types.rs @@ -1,3 +1,4 @@ +use crate::api::PlayStoneRequest; use std::time::Duration; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -21,13 +22,24 @@ impl Default for Size { } } -pub(crate) struct AppState { - game: Option, +pub struct AppState { + pub game: Option, } impl AppState { pub fn new() -> Self { - Self { game: None } + Self { + game: Some(GameState::new()), + } + } + + pub fn place_stone(&mut self, req: PlayStoneRequest) { + match self.game { + Some(ref mut game) => { + game.place_stone(req.column, req.row); + } + None => {} + } } } @@ -55,18 +67,82 @@ impl From for String { } } -struct Player { +pub struct Player { name: String, rank: Rank, } -pub(crate) struct GameState { - goban: Vec>, - conversation: Vec, +pub struct GameState { + pub board: Board, + pub conversation: Vec, + pub current_player: Color, - white_player: Player, - black_player: Player, + pub white_player: Player, + pub black_player: Player, - white_clock: Duration, - black_clock: Duration, + pub white_clock: Duration, + pub black_clock: Duration, +} + +impl GameState { + fn new() -> GameState { + GameState { + board: Board::new(), + conversation: vec![], + current_player: Color::Black, + white_player: Player { + name: "Savanni".to_owned(), + rank: Rank::Kyu(10), + }, + black_player: Player { + name: "Opal".to_owned(), + rank: Rank::Kyu(10), + }, + white_clock: Duration::from_secs(600), + black_clock: Duration::from_secs(600), + } + } + + fn place_stone(&mut self, column: u8, row: u8) { + self.board.place_stone(column, row, self.current_player); + match self.current_player { + Color::White => self.current_player = Color::Black, + Color::Black => self.current_player = Color::White, + } + } +} + +pub struct Board { + pub size: Size, + pub spaces: Vec>, +} + +impl Board { + fn new() -> Self { + let mut spaces = Vec::new(); + for _ in 0..19 * 19 { + spaces.push(None); + } + Self { + size: Size { + width: 19, + height: 19, + }, + spaces, + } + } + + pub fn place_stone(&mut self, column: u8, row: u8, stone: Color) { + let addr = self.addr(column, row); + self.spaces[addr] = Some(stone); + } + + pub fn stone(&self, column: u8, row: u8) -> Option { + let addr = self.addr(column, row); + self.spaces[addr] + } + + fn addr(&self, column: u8, row: u8) -> usize { + ((row as usize) * (self.size.width as usize) + (column as usize)) as usize + } } diff --git a/kifu/kifu-core/src/ui/playing_field.rs b/kifu/kifu-core/src/ui/playing_field.rs index 70cc951..7398744 100644 --- a/kifu/kifu-core/src/ui/playing_field.rs +++ b/kifu/kifu-core/src/ui/playing_field.rs @@ -1,5 +1,5 @@ use crate::types::{Color, Size}; -use crate::ui::types; +use crate::{types::GameState, ui::types}; #[derive(Clone, Debug)] pub struct PlayingFieldView { @@ -11,8 +11,8 @@ pub struct PlayingFieldView { pub current_player: Color, } -pub fn playing_field() -> PlayingFieldView { - let board = types::BoardElement::default(); +pub fn playing_field(game: &GameState) -> PlayingFieldView { + let board = types::BoardElement::from(&game.board); let player_card_black = types::PlayerCardElement { color: Color::Black, @@ -42,6 +42,6 @@ pub fn playing_field() -> PlayingFieldView { player_card_white, chat, message, - current_player: Color::White, + current_player: game.current_player, } } diff --git a/kifu/kifu-core/src/ui/types.rs b/kifu/kifu-core/src/ui/types.rs index f607356..39e2904 100644 --- a/kifu/kifu-core/src/ui/types.rs +++ b/kifu/kifu-core/src/ui/types.rs @@ -1,4 +1,8 @@ use crate::types::{Color, Size}; +use crate::{ + api::{PlayStoneRequest, Request}, + types::Board, +}; #[derive(Clone, Copy, Debug, PartialEq)] pub struct Jitter { @@ -21,16 +25,10 @@ impl StoneElement { } } -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct PlayStoneRequest { - col: u8, - row: u8, -} - #[derive(Clone, Copy, Debug, PartialEq)] pub enum IntersectionElement { Unplayable, - Empty(PlayStoneRequest), + Empty(Request), Filled(StoneElement), } @@ -45,7 +43,12 @@ impl BoardElement { let spaces: Vec = (0..size.height) .map(|row| { (0..size.width) - .map(|col| IntersectionElement::Empty(PlayStoneRequest { col, row })) + .map(|column| { + IntersectionElement::Empty(Request::PlayStoneRequest(PlayStoneRequest { + column, + row, + })) + }) .collect::>() }) .collect::>>() @@ -53,17 +56,38 @@ impl BoardElement { Self { size, spaces } } - pub fn stone_mut<'a>(&'a mut self, row: u8, col: u8) -> &'a mut IntersectionElement { - let addr = self.addr(row, col); - &mut self.spaces[addr] + pub fn stone(&self, column: u8, row: u8) -> IntersectionElement { + let addr = self.addr(column, row); + self.spaces[addr] } - pub fn stone(&self, row: u8, col: u8) -> IntersectionElement { - self.spaces[self.addr(row, col)].clone() + fn addr(&self, column: u8, row: u8) -> usize { + ((row as usize) * (self.size.width as usize) + (column as usize)) as usize } +} - fn addr(&self, row: u8, col: u8) -> usize { - ((row as usize) * (self.size.width as usize) + (col as usize)) as usize +impl From<&Board> for BoardElement { + fn from(board: &Board) -> Self { + let spaces: Vec = (0..board.size.height) + .map(|row| { + (0..board.size.width) + .map(|column| match board.stone(column, row) { + Some(color) => IntersectionElement::Filled(StoneElement { + jitter: Jitter { x: 0, y: 0 }, + color, + }), + None => IntersectionElement::Empty(Request::PlayStoneRequest( + PlayStoneRequest { column, row }, + )), + }) + .collect::>() + }) + .collect::>>() + .concat(); + Self { + size: board.size, + spaces, + } } } diff --git a/kifu/kifu-gtk/src/lib.rs b/kifu/kifu-gtk/src/lib.rs index 6bae95d..bc9d45c 100644 --- a/kifu/kifu-gtk/src/lib.rs +++ b/kifu/kifu-gtk/src/lib.rs @@ -1 +1,21 @@ pub mod ui; +use kifu_core::{CoreApp, Request, Response}; +use std::sync::Arc; +use tokio::runtime::Runtime; + +#[derive(Clone)] +pub struct CoreApi { + pub gtk_tx: gtk::glib::Sender, + pub rt: Arc, + pub core: CoreApp, +} + +impl CoreApi { + pub fn dispatch(&self, request: Request) { + self.rt.spawn({ + let gtk_tx = self.gtk_tx.clone(); + let core = self.core.clone(); + async move { gtk_tx.send(core.dispatch(request).await) } + }); + } +} diff --git a/kifu/kifu-gtk/src/main.rs b/kifu/kifu-gtk/src/main.rs index a12ed30..0a7e41c 100644 --- a/kifu/kifu-gtk/src/main.rs +++ b/kifu/kifu-gtk/src/main.rs @@ -1,7 +1,7 @@ use gio::resources_lookup_data; use gtk::prelude::*; use kifu_core::{CoreApp, Request, Response}; -use kifu_gtk::ui::PlayingField; +use kifu_gtk::{ui::PlayingField, CoreApi}; use std::{ sync::{Arc, Mutex}, time::Duration, @@ -11,23 +11,6 @@ use tokio::{ sync::mpsc::{Receiver, Sender}, }; -#[derive(Clone)] -pub struct CoreApi { - gtk_tx: gtk::glib::Sender, - rt: Arc, - core: CoreApp, -} - -impl CoreApi { - pub fn dispatch(&self, request: Request) { - self.rt.spawn({ - let gtk_tx = self.gtk_tx.clone(); - let core = self.core.clone(); - async move { gtk_tx.send(core.dispatch(request).await) } - }); - } -} - fn main() { gio::resources_register_include!("com.luminescent-dreams.kifu-gtk.gresource") .expect("Failed to register resources"); @@ -67,15 +50,18 @@ fn main() { let window = gtk::ApplicationWindow::new(app); window.present(); - gtk_rx.attach(None, move |message| { - println!("message: {:?}", message); - match message { - Response::PlayingFieldView(view) => { - let playing_field = PlayingField::new(view); - window.set_child(Some(&playing_field)); + gtk_rx.attach(None, { + let api = api.clone(); + move |message| { + match message { + Response::PlayingFieldView(view) => { + let api = api.clone(); + let playing_field = PlayingField::new(api, view); + window.set_child(Some(&playing_field)); + } } + Continue(true) } - Continue(true) }); api.dispatch(Request::PlayingField); diff --git a/kifu/kifu-gtk/src/ui/board.rs b/kifu/kifu-gtk/src/ui/board.rs index 32efb75..6024b42 100644 --- a/kifu/kifu-gtk/src/ui/board.rs +++ b/kifu/kifu-gtk/src/ui/board.rs @@ -1,3 +1,4 @@ +use crate::CoreApi; use gio::resources_lookup_data; use glib::Object; use gtk::{ @@ -27,6 +28,8 @@ pub struct BoardPrivate { current_player: Rc>, board: Rc>, cursor_location: Rc>, + + api: Rc>>, } #[glib::object_subclass] @@ -41,6 +44,7 @@ impl ObjectSubclass for BoardPrivate { current_player: Rc::new(RefCell::new(Color::Black)), board: Default::default(), cursor_location: Default::default(), + api: Default::default(), } } } @@ -169,12 +173,17 @@ impl ObjectImpl for BoardPrivate { { let board = self.board.clone(); let cursor = self.cursor_location.clone(); + let api = self.api.clone(); gesture.connect_released(move |_, _, _, _| { let board = board.borrow(); let cursor = cursor.borrow(); match board.stone(cursor.row, cursor.column) { IntersectionElement::Empty(request) => { - println!("need to send request: {:?}", request) + println!("need to send request: {:?}", request); + api.borrow() + .as_ref() + .expect("API must exist") + .dispatch(request); } _ => {} } @@ -193,9 +202,12 @@ glib::wrapper! { } impl Board { - pub fn new() -> Self { + pub fn new(api: CoreApi) -> Self { let s: Self = Object::builder().build(); + + *s.imp().api.borrow_mut() = Some(api); s.attach(&s.imp().drawing_area, 1, 1, 1, 1); + s } diff --git a/kifu/kifu-gtk/src/ui/playing_field.rs b/kifu/kifu-gtk/src/ui/playing_field.rs index ac6708b..b4fa490 100644 --- a/kifu/kifu-gtk/src/ui/playing_field.rs +++ b/kifu/kifu-gtk/src/ui/playing_field.rs @@ -1,4 +1,5 @@ use crate::ui::{Board, Chat, PlayerCard}; +use crate::CoreApi; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use kifu_core::{ @@ -23,7 +24,7 @@ pub struct PlayingFieldView { */ pub struct PlayingFieldPrivate { - goban: Board, + board: Rc>>, player_card_white: Rc>>, player_card_black: Rc>>, chat: Rc>>, @@ -32,7 +33,7 @@ pub struct PlayingFieldPrivate { impl Default for PlayingFieldPrivate { fn default() -> Self { Self { - goban: Board::new(), + board: Default::default(), player_card_white: Rc::new(RefCell::new(None)), player_card_black: Rc::new(RefCell::new(None)), chat: Rc::new(RefCell::new(None)), @@ -45,15 +46,6 @@ impl ObjectSubclass for PlayingFieldPrivate { const NAME: &'static str = "PlayingField"; type Type = PlayingField; type ParentType = gtk::Grid; - - fn new() -> Self { - Self { - goban: Board::new(), - player_card_white: Rc::new(RefCell::new(None)), - player_card_black: Rc::new(RefCell::new(None)), - chat: Rc::new(RefCell::new(None)), - } - } } impl ObjectImpl for PlayingFieldPrivate {} @@ -65,16 +57,19 @@ glib::wrapper! { } impl PlayingField { - pub fn new(view: PlayingFieldView) -> PlayingField { + pub fn new(api: CoreApi, view: PlayingFieldView) -> PlayingField { let s: Self = Object::builder().build(); let player_card_white = PlayerCard::new(view.player_card_white); let player_card_black = PlayerCard::new(view.player_card_black); let chat = Chat::new(view.chat); - s.imp().goban.set_board(view.board); - - s.attach(&s.imp().goban, 1, 1, 1, 2); + *s.imp().board.borrow_mut() = Some(Board::new(api)); + s.imp() + .board + .borrow() + .as_ref() + .map(|board| s.attach(board, 1, 1, 1, 2)); s.attach(&player_card_black, 2, 1, 1, 1); s.attach(&player_card_white, 3, 1, 1, 1); s.attach(&chat, 2, 2, 2, 1); @@ -82,7 +77,11 @@ impl PlayingField { *s.imp().player_card_white.borrow_mut() = Some(player_card_white); *s.imp().player_card_black.borrow_mut() = Some(player_card_black); *s.imp().chat.borrow_mut() = Some(chat); - s.imp().goban.set_current_player(view.current_player); + + s.imp().board.borrow().as_ref().map(|board| { + board.set_board(view.board); + board.set_current_player(view.current_player); + }); s } -- 2.44.1 From 59a8f252da7fdafe07b5c5cb9a3d90b6b3abeaee Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 6 Apr 2023 22:13:09 -0400 Subject: [PATCH 5/7] Optimize board updates --- flake.nix | 2 +- kifu/kifu-gtk/src/main.rs | 17 ++++++++++++++--- kifu/kifu-gtk/src/ui/board.rs | 6 ++++-- kifu/kifu-gtk/src/ui/playing_field.rs | 7 +++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/flake.nix b/flake.nix index 138422f..a5ff1b9 100644 --- a/flake.nix +++ b/flake.nix @@ -76,7 +76,7 @@ inherit pkgs; buildRustCrateForPkgs = customBuildInfo; rootFeatures = [ "screenplay" ]; - release = false; + release = true; }).rootCrate.build; }; }; diff --git a/kifu/kifu-gtk/src/main.rs b/kifu/kifu-gtk/src/main.rs index 0a7e41c..d1ce77d 100644 --- a/kifu/kifu-gtk/src/main.rs +++ b/kifu/kifu-gtk/src/main.rs @@ -3,7 +3,7 @@ use gtk::prelude::*; use kifu_core::{CoreApp, Request, Response}; use kifu_gtk::{ui::PlayingField, CoreApi}; use std::{ - sync::{Arc, Mutex}, + sync::{Arc, Mutex, RwLock}, time::Duration, }; use tokio::{ @@ -52,12 +52,23 @@ fn main() { gtk_rx.attach(None, { let api = api.clone(); + let playing_field = Arc::new(RwLock::new(None)); move |message| { match message { Response::PlayingFieldView(view) => { let api = api.clone(); - let playing_field = PlayingField::new(api, view); - window.set_child(Some(&playing_field)); + + let start = std::time::Instant::now(); + let mut playing_field = playing_field.write().unwrap(); + if playing_field.is_none() { + let field = PlayingField::new(api, view); + window.set_child(Some(&field)); + *playing_field = Some(field); + } else { + playing_field.as_ref().map(|field| field.update_view(view)); + } + let end = std::time::Instant::now(); + println!("Time to render the playing field: {:?}", end - start); } } Continue(true) diff --git a/kifu/kifu-gtk/src/ui/board.rs b/kifu/kifu-gtk/src/ui/board.rs index 6024b42..de9b561 100644 --- a/kifu/kifu-gtk/src/ui/board.rs +++ b/kifu/kifu-gtk/src/ui/board.rs @@ -82,6 +82,7 @@ impl ObjectImpl for BoardPrivate { self.drawing_area .set_draw_func(move |_, context, width, height| { + let render_start = std::time::Instant::now(); let board = board.borrow(); match background { Ok(Some(ref background)) => { @@ -125,11 +126,10 @@ impl ObjectImpl for BoardPrivate { (0..19).for_each(|col| { (0..19).for_each(|row| { match board.stone(row, col) { - IntersectionElement::Unplayable => {} - IntersectionElement::Empty(request) => {} IntersectionElement::Filled(stone) => { pen.stone(&context, row, col, stone.color); } + _ => {} }; }) }); @@ -144,6 +144,8 @@ impl ObjectImpl for BoardPrivate { ), _ => {} } + let render_end = std::time::Instant::now(); + println!("board rendering time: {:?}", render_end - render_start); }); let motion_controller = gtk::EventControllerMotion::new(); diff --git a/kifu/kifu-gtk/src/ui/playing_field.rs b/kifu/kifu-gtk/src/ui/playing_field.rs index b4fa490..1738cf3 100644 --- a/kifu/kifu-gtk/src/ui/playing_field.rs +++ b/kifu/kifu-gtk/src/ui/playing_field.rs @@ -85,6 +85,13 @@ impl PlayingField { s } + + pub fn update_view(&self, view: PlayingFieldView) { + self.imp().board.borrow().as_ref().map(|board| { + board.set_board(view.board); + board.set_current_player(view.current_player); + }); + } } #[cfg(feature = "screenplay")] -- 2.44.1 From 26db170c790789c21fc62dc7063a07f61dd04902 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 6 Apr 2023 22:29:15 -0400 Subject: [PATCH 6/7] Do some cleanups and allow the cursor to move off the board --- kifu/kifu-core/src/lib.rs | 2 - kifu/kifu-core/src/ui/playing_field.rs | 2 +- kifu/kifu-gtk/src/main.rs | 10 +---- kifu/kifu-gtk/src/ui/board.rs | 57 +++++++++++++++----------- kifu/kifu-gtk/src/ui/player_card.rs | 2 +- kifu/kifu-gtk/src/ui/playing_field.rs | 20 +-------- 6 files changed, 38 insertions(+), 55 deletions(-) diff --git a/kifu/kifu-core/src/lib.rs b/kifu/kifu-core/src/lib.rs index a13d2c8..9c22bc8 100644 --- a/kifu/kifu-core/src/lib.rs +++ b/kifu/kifu-core/src/lib.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - mod api; pub use api::{CoreApp, Request, Response}; diff --git a/kifu/kifu-core/src/ui/playing_field.rs b/kifu/kifu-core/src/ui/playing_field.rs index 7398744..cdc9c12 100644 --- a/kifu/kifu-core/src/ui/playing_field.rs +++ b/kifu/kifu-core/src/ui/playing_field.rs @@ -1,4 +1,4 @@ -use crate::types::{Color, Size}; +use crate::types::Color; use crate::{types::GameState, ui::types}; #[derive(Clone, Debug)] diff --git a/kifu/kifu-gtk/src/main.rs b/kifu/kifu-gtk/src/main.rs index d1ce77d..2ede4f3 100644 --- a/kifu/kifu-gtk/src/main.rs +++ b/kifu/kifu-gtk/src/main.rs @@ -1,15 +1,7 @@ -use gio::resources_lookup_data; use gtk::prelude::*; use kifu_core::{CoreApp, Request, Response}; use kifu_gtk::{ui::PlayingField, CoreApi}; -use std::{ - sync::{Arc, Mutex, RwLock}, - time::Duration, -}; -use tokio::{ - runtime::Runtime, - sync::mpsc::{Receiver, Sender}, -}; +use std::sync::{Arc, RwLock}; fn main() { gio::resources_register_include!("com.luminescent-dreams.kifu-gtk.gresource") diff --git a/kifu/kifu-gtk/src/ui/board.rs b/kifu/kifu-gtk/src/ui/board.rs index de9b561..4ededd6 100644 --- a/kifu/kifu-gtk/src/ui/board.rs +++ b/kifu/kifu-gtk/src/ui/board.rs @@ -27,7 +27,7 @@ pub struct BoardPrivate { current_player: Rc>, board: Rc>, - cursor_location: Rc>, + cursor_location: Rc>>, api: Rc>>, } @@ -135,14 +135,17 @@ impl ObjectImpl for BoardPrivate { }); let cursor = cursor_location.borrow(); - match board.stone(cursor.row, cursor.column) { - IntersectionElement::Empty(_) => pen.ghost_stone( - context, - cursor.row, - cursor.column, - *current_player.borrow(), - ), - _ => {} + match *cursor { + None => {} + Some(ref cursor) => match board.stone(cursor.row, cursor.column) { + IntersectionElement::Empty(_) => pen.ghost_stone( + context, + cursor.row, + cursor.column, + *current_player.borrow(), + ), + _ => {} + }, } let render_end = std::time::Instant::now(); println!("board rendering time: {:?}", render_end - render_start); @@ -159,12 +162,17 @@ impl ObjectImpl for BoardPrivate { let hspace_between = ((WIDTH - 40) as f64) / ((board.size.width - 1) as f64); let vspace_between = ((HEIGHT - 40) as f64) / ((board.size.height - 1) as f64); - let addr = Addr { - column: ((x.round() - 20.) / hspace_between).round() as u8, - row: ((y.round() - 20.) / vspace_between).round() as u8, - }; + let addr = + if x.round() < 20. || x.round() > 780. || y.round() < 20. || y.round() > 780. { + None + } else { + Some(Addr { + column: ((x.round() - 20.) / hspace_between).round() as u8, + row: ((y.round() - 20.) / vspace_between).round() as u8, + }) + }; - if *cursor != addr { + if *cursor != addr.clone() { *cursor = addr; drawing_area.queue_draw(); } @@ -179,15 +187,18 @@ impl ObjectImpl for BoardPrivate { gesture.connect_released(move |_, _, _, _| { let board = board.borrow(); let cursor = cursor.borrow(); - match board.stone(cursor.row, cursor.column) { - IntersectionElement::Empty(request) => { - println!("need to send request: {:?}", request); - api.borrow() - .as_ref() - .expect("API must exist") - .dispatch(request); - } - _ => {} + match *cursor { + None => {} + Some(ref cursor) => match board.stone(cursor.row, cursor.column) { + IntersectionElement::Empty(request) => { + println!("need to send request: {:?}", request); + api.borrow() + .as_ref() + .expect("API must exist") + .dispatch(request); + } + _ => {} + }, } }); } diff --git a/kifu/kifu-gtk/src/ui/player_card.rs b/kifu/kifu-gtk/src/ui/player_card.rs index 04e10e2..c10012c 100644 --- a/kifu/kifu-gtk/src/ui/player_card.rs +++ b/kifu/kifu-gtk/src/ui/player_card.rs @@ -1,6 +1,6 @@ use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use kifu_core::{ui::PlayerCardElement, Color}; +use kifu_core::ui::PlayerCardElement; #[derive(Default)] pub struct PlayerCardPrivate { diff --git a/kifu/kifu-gtk/src/ui/playing_field.rs b/kifu/kifu-gtk/src/ui/playing_field.rs index 1738cf3..afcb93d 100644 --- a/kifu/kifu-gtk/src/ui/playing_field.rs +++ b/kifu/kifu-gtk/src/ui/playing_field.rs @@ -2,27 +2,9 @@ use crate::ui::{Board, Chat, PlayerCard}; use crate::CoreApi; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use kifu_core::{ - ui::{ - BoardElement, ChatElement, IntersectionElement, PlayerCardElement, PlayingFieldView, - StoneElement, TextFieldElement, - }, - Color, Size, -}; +use kifu_core::ui::PlayingFieldView; use std::{cell::RefCell, rc::Rc}; -/* -#[derive(Clone, Debug)] -pub struct PlayingFieldView { - pub board: types::GobanElement, - pub player_card_black: types::PlayerCardElement, - pub player_card_white: types::PlayerCardElement, - pub chat: types::ChatElement, - pub message: types::TextFieldElement, - pub current_player: Color, -} -*/ - pub struct PlayingFieldPrivate { board: Rc>>, player_card_white: Rc>>, -- 2.44.1 From 4590bc6688b22058dec944b00b811c58d56dadb4 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 6 Apr 2023 23:26:46 -0400 Subject: [PATCH 7/7] Adjust margins and the size of the stones --- kifu/kifu-gtk/src/ui/board.rs | 69 +++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/kifu/kifu-gtk/src/ui/board.rs b/kifu/kifu-gtk/src/ui/board.rs index 4ededd6..7b442f5 100644 --- a/kifu/kifu-gtk/src/ui/board.rs +++ b/kifu/kifu-gtk/src/ui/board.rs @@ -15,6 +15,7 @@ use std::{cell::RefCell, io::Cursor, rc::Rc}; const WIDTH: i32 = 800; const HEIGHT: i32 = 800; +const MARGIN: i32 = 20; #[derive(Clone, Default, PartialEq)] struct Addr { @@ -84,6 +85,7 @@ impl ObjectImpl for BoardPrivate { .set_draw_func(move |_, context, width, height| { let render_start = std::time::Instant::now(); let board = board.borrow(); + match background { Ok(Some(ref background)) => { context.set_source_pixbuf(&background, 0., 0.); @@ -99,20 +101,32 @@ impl ObjectImpl for BoardPrivate { let vspace_between = ((height - 40) as f64) / ((board.size.height - 1) as f64); let pen = Pen { - x_offset: 20., - y_offset: 20., + x_offset: MARGIN as f64, + y_offset: MARGIN as f64, hspace_between, vspace_between, }; (0..board.size.width).for_each(|col| { - context.move_to(20.0 + (col as f64) * hspace_between, 20.0); - context.line_to(20.0 + (col as f64) * hspace_between, (height as f64) - 20.0); + context.move_to( + (MARGIN as f64) + (col as f64) * hspace_between, + MARGIN as f64, + ); + context.line_to( + (MARGIN as f64) + (col as f64) * hspace_between, + (height as f64) - (MARGIN as f64), + ); let _ = context.stroke(); }); (0..board.size.height).for_each(|row| { - context.move_to(20.0, 20.0 + (row as f64) * vspace_between); - context.line_to((width - 20) as f64, 20.0 + (row as f64) * vspace_between); + context.move_to( + MARGIN as f64, + (MARGIN as f64) + (row as f64) * vspace_between, + ); + context.line_to( + (width - MARGIN) as f64, + (MARGIN as f64) + (row as f64) * vspace_between, + ); let _ = context.stroke(); }); @@ -162,15 +176,18 @@ impl ObjectImpl for BoardPrivate { let hspace_between = ((WIDTH - 40) as f64) / ((board.size.width - 1) as f64); let vspace_between = ((HEIGHT - 40) as f64) / ((board.size.height - 1) as f64); - let addr = - if x.round() < 20. || x.round() > 780. || y.round() < 20. || y.round() > 780. { - None - } else { - Some(Addr { - column: ((x.round() - 20.) / hspace_between).round() as u8, - row: ((y.round() - 20.) / vspace_between).round() as u8, - }) - }; + let addr = if x.round() < MARGIN as f64 + || x.round() > (WIDTH - MARGIN) as f64 + || y.round() < MARGIN as f64 + || y.round() > (HEIGHT - MARGIN) as f64 + { + None + } else { + Some(Addr { + column: ((x.round() - MARGIN as f64) / hspace_between).round() as u8, + row: ((y.round() - MARGIN as f64) / vspace_between).round() as u8, + }) + }; if *cursor != addr.clone() { *cursor = addr; @@ -246,7 +263,7 @@ impl Pen { context.arc( self.x_offset + (col as f64) * self.hspace_between, self.y_offset + (row as f64) * self.vspace_between, - 10., + 5., 0., 2. * std::f64::consts::PI, ); @@ -258,14 +275,7 @@ impl Pen { Color::White => context.set_source_rgb(0.9, 0.9, 0.9), Color::Black => context.set_source_rgb(0.0, 0.0, 0.0), }; - context.arc( - 20.0 + (col as f64) * self.hspace_between, - 20.0 + (row as f64) * self.vspace_between, - 25.0, - 0.0, - 2.0 * std::f64::consts::PI, - ); - let _ = context.fill(); + self.draw_stone(context, row, col); } fn ghost_stone(&self, context: &cairo::Context, row: u8, col: u8, color: Color) { @@ -273,10 +283,15 @@ impl Pen { Color::White => context.set_source_rgba(0.9, 0.9, 0.9, 0.5), Color::Black => context.set_source_rgba(0.0, 0.0, 0.0, 0.5), }; + self.draw_stone(context, row, col); + } + + fn draw_stone(&self, context: &cairo::Context, row: u8, col: u8) { + let radius = self.hspace_between / 2. - 2.; context.arc( - 20.0 + (col as f64) * self.hspace_between, - 20.0 + (row as f64) * self.vspace_between, - 25.0, + self.x_offset + (col as f64) * self.hspace_between, + self.y_offset + (row as f64) * self.vspace_between, + radius, 0.0, 2.0 * std::f64::consts::PI, ); -- 2.44.1