From 8df649d6cd6ba8bc07dd16a1e4ee786e60b52887 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 6 Apr 2023 21:52:39 -0400 Subject: [PATCH] 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 }