From ef045117befcd13f38e81fb5df2effa2cee9aabe Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 26 May 2023 00:16:40 -0400 Subject: [PATCH 1/7] Start on the NewGame page --- kifu/core/src/api.rs | 27 +++++++++++- kifu/core/src/types.rs | 5 ++- kifu/core/src/ui/launch_screen.rs | 14 +++++++ kifu/core/src/ui/mod.rs | 6 +++ kifu/core/src/ui/new_game.rs | 70 +++++++++++++++++++++++++++++++ kifu/gtk/src/main.rs | 63 +++++++++++++++++++--------- kifu/gtk/src/ui/mod.rs | 3 ++ kifu/gtk/src/ui/new_game.rs | 64 ++++++++++++++++++++++++++++ 8 files changed, 229 insertions(+), 23 deletions(-) create mode 100644 kifu/core/src/ui/launch_screen.rs create mode 100644 kifu/core/src/ui/new_game.rs create mode 100644 kifu/gtk/src/ui/new_game.rs diff --git a/kifu/core/src/api.rs b/kifu/core/src/api.rs index 8635268..401cbb9 100644 --- a/kifu/core/src/api.rs +++ b/kifu/core/src/api.rs @@ -1,5 +1,5 @@ use crate::types::AppState; -use crate::ui::{playing_field, PlayingFieldView}; +use crate::ui::{new_game, playing_field, NewGameView, PlayingFieldView}; use serde::{Deserialize, Serialize}; use std::sync::{Arc, RwLock}; use typeshare::typeshare; @@ -8,8 +8,12 @@ use typeshare::typeshare; #[typeshare] #[serde(tag = "type", content = "content")] pub enum CoreRequest { + // CreateGameRequest(CreateGameRequest), + LaunchScreen, + NewGame, PlayingField, PlayStoneRequest(PlayStoneRequest), + StartGame, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -23,6 +27,7 @@ pub struct PlayStoneRequest { #[typeshare] #[serde(tag = "type", content = "content")] pub enum CoreResponse { + NewGameView(NewGameView), PlayingFieldView(PlayingFieldView), } @@ -40,6 +45,23 @@ impl CoreApp { pub async fn dispatch(&self, request: CoreRequest) -> CoreResponse { match request { + /* + CoreRequest::LaunchScreen => { + let app_state = self.state.read().unwrap(); + + At launch, I want to either show a list of games in progress, the current game, or the game creation screen. + - if a live game is in progress, immmediately go to that game. Such a game will be classified at game creation, so it should be persisted to the state. + - if no live games are in progress, but there are slow games in progress, show a list of the slow games and let the player choose which one to jump into. + - if no games are in progress, show only the game creation screen + - game creation menu should be present both when there are only slow games and when there are no games + - the UI returned here will always be available in other places, such as when the user is viewing a game and wants to return to this page + + For the initial version, I want only to show the game creation screen. Then I will backtrack record application state so that the only decisions can be made. + } + */ + // CoreRequest::LaunchScreen => CoreResponse::NewGameView(new_game()), + CoreRequest::LaunchScreen => CoreResponse::NewGameView(new_game()), + CoreRequest::NewGame => CoreResponse::NewGameView(new_game()), CoreRequest::PlayingField => { let app_state = self.state.read().unwrap(); let game = app_state.game.as_ref().unwrap(); @@ -52,6 +74,9 @@ impl CoreApp { let game = app_state.game.as_ref().unwrap(); CoreResponse::PlayingFieldView(playing_field(game)) } + CoreRequest::StartGame => { + unimplemented!() + } } } diff --git a/kifu/core/src/types.rs b/kifu/core/src/types.rs index 3a1b3f3..5747c07 100644 --- a/kifu/core/src/types.rs +++ b/kifu/core/src/types.rs @@ -65,8 +65,9 @@ impl AppState { } } -#[derive(Debug)] -enum Rank { +#[derive(Clone, Debug, Serialize, Deserialize)] +#[typeshare] +pub enum Rank { Kyu(u8), Dan(u8), Pro(u8), diff --git a/kifu/core/src/ui/launch_screen.rs b/kifu/core/src/ui/launch_screen.rs new file mode 100644 index 0000000..3696423 --- /dev/null +++ b/kifu/core/src/ui/launch_screen.rs @@ -0,0 +1,14 @@ +use crate::{ + ui::types; +}; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum LaunchScreenView { + CreateGame(CreateGameView) +} + +// This will be called when the Kifu application starts. +pub fn launch_screen() -> LaunchScreenView { +} diff --git a/kifu/core/src/ui/mod.rs b/kifu/core/src/ui/mod.rs index abe5612..f7f43e1 100644 --- a/kifu/core/src/ui/mod.rs +++ b/kifu/core/src/ui/mod.rs @@ -1,6 +1,12 @@ mod playing_field; pub use playing_field::{playing_field, PlayingFieldView}; +// mod launch_screen; +// pub use launch_screen::{launch_screen, LaunchScreenView}; + +mod new_game; +pub use new_game::{new_game, Menu, NewGameView, Player}; + mod types; pub use types::{ BoardElement, ChatElement, IntersectionElement, PlayerCardElement, StoneElement, diff --git a/kifu/core/src/ui/new_game.rs b/kifu/core/src/ui/new_game.rs new file mode 100644 index 0000000..e95cf70 --- /dev/null +++ b/kifu/core/src/ui/new_game.rs @@ -0,0 +1,70 @@ +use crate::types::Rank; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[typeshare] +pub struct Menu { + current: Option, + options: Vec, +} + +impl Menu { + pub fn new(options: Vec, current: Option) -> Menu { + if let Some(ref current) = current { + assert!(options.contains(current)); + } + Menu { current, options } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[typeshare] +pub enum Player { + Hotseat(HotseatPlayer), + Remote(RemotePlayer), + Bot(BotPlayer), +} + +impl Default for Player { + fn default() -> Player { + Player::Hotseat(HotseatPlayer::default()) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +#[typeshare] +pub struct HotseatPlayer { + pub name: Option, + pub rank: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[typeshare] +pub struct RemotePlayer {} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[typeshare] +pub struct BotPlayer {} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[typeshare] +pub struct NewGameView { + pub black_player: Player, + pub white_player: Player, +} + +pub fn new_game() -> NewGameView { + let black_player = Player::Hotseat(HotseatPlayer { + name: Some("black_player".to_owned()), + ..Default::default() + }); + let white_player = Player::Hotseat(HotseatPlayer { + name: Some("white_player".to_owned()), + ..Default::default() + }); + NewGameView { + black_player, + white_player, + } +} diff --git a/kifu/gtk/src/main.rs b/kifu/gtk/src/main.rs index 931aae5..b9a21a4 100644 --- a/kifu/gtk/src/main.rs +++ b/kifu/gtk/src/main.rs @@ -1,8 +1,46 @@ use gtk::prelude::*; use kifu_core::{CoreApp, CoreRequest, CoreResponse}; -use kifu_gtk::{ui::PlayingField, CoreApi}; +use kifu_gtk::{ + ui::{NewGame, PlayingField}, + CoreApi, +}; use std::sync::{Arc, RwLock}; +fn perftrace(trace_name: &str, f: F) -> A +where + F: FnOnce() -> A, +{ + let start = std::time::Instant::now(); + let result = f(); + let end = std::time::Instant::now(); + println!("[Trace: {}] {:?}", trace_name, end - start); + return result; +} + +fn handle_response(api: CoreApi, window: gtk::ApplicationWindow, message: CoreResponse) { + let playing_field = Arc::new(RwLock::new(None)); + match message { + CoreResponse::NewGameView(view) => { + let api = api.clone(); + + let new_game = NewGame::new(api, view); + window.set_child(Some(&new_game)); + } + CoreResponse::PlayingFieldView(view) => { + let api = api.clone(); + + 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)); + } + } + } +} + fn main() { gio::resources_register_include!("com.luminescent-dreams.kifu-gtk.gresource") .expect("Failed to register resources"); @@ -44,30 +82,15 @@ fn main() { gtk_rx.attach(None, { let api = api.clone(); - let playing_field = Arc::new(RwLock::new(None)); move |message| { - match message { - CoreResponse::PlayingFieldView(view) => { - let api = api.clone(); - - 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); - } - } + perftrace("handle_response", || { + handle_response(api.clone(), window.clone(), message) + }); Continue(true) } }); - api.dispatch(CoreRequest::PlayingField); + api.dispatch(CoreRequest::NewGame); } }); diff --git a/kifu/gtk/src/ui/mod.rs b/kifu/gtk/src/ui/mod.rs index 6a469b3..7fa21fe 100644 --- a/kifu/gtk/src/ui/mod.rs +++ b/kifu/gtk/src/ui/mod.rs @@ -7,6 +7,9 @@ pub use chat::Chat; mod playing_field; pub use playing_field::PlayingField; +mod new_game; +pub use new_game::NewGame; + mod board; pub use board::Board; diff --git a/kifu/gtk/src/ui/new_game.rs b/kifu/gtk/src/ui/new_game.rs new file mode 100644 index 0000000..d6dcaf4 --- /dev/null +++ b/kifu/gtk/src/ui/new_game.rs @@ -0,0 +1,64 @@ +use crate::CoreApi; +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; +use kifu_core::ui::{NewGameView, Player}; +use std::{cell::RefCell, rc::Rc}; + +pub struct NewGamePrivate { + black_player: gtk::Label, + white_player: gtk::Label, +} + +impl Default for NewGamePrivate { + fn default() -> Self { + Self { + black_player: gtk::Label::new(None), + white_player: gtk::Label::new(None), + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for NewGamePrivate { + const NAME: &'static str = "NewGame"; + type Type = NewGame; + type ParentType = gtk::Grid; +} + +impl ObjectImpl for NewGamePrivate {} +impl WidgetImpl for NewGamePrivate {} +impl GridImpl for NewGamePrivate {} + +glib::wrapper! { + pub struct NewGame(ObjectSubclass) @extends gtk::Grid, gtk::Widget; +} + +impl NewGame { + pub fn new(_api: CoreApi, view: NewGameView) -> NewGame { + let s: Self = Object::builder().build(); + + match view.black_player { + Player::Hotseat(player) => { + if let Some(name) = player.name { + s.imp().black_player.set_text(name.as_ref()); + } + } + Player::Remote(_) => s.imp().black_player.set_text("remote player"), + Player::Bot(_) => s.imp().black_player.set_text("bot player"), + } + s.attach(&s.imp().black_player, 1, 1, 1, 1); + + match view.white_player { + Player::Hotseat(player) => { + if let Some(name) = player.name { + s.imp().white_player.set_text(name.as_ref()); + } + } + Player::Remote(_) => s.imp().black_player.set_text("remote player"), + Player::Bot(_) => s.imp().black_player.set_text("bot player"), + } + s.attach(&s.imp().white_player, 2, 1, 1, 1); + + s + } +} -- 2.44.1 From 6456389eef80e60ebec8d7c26b384d3e99fbe7ac Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 14 Jun 2023 23:47:12 -0400 Subject: [PATCH 2/7] Apply performance tracing to various functions --- kifu/gtk/src/lib.rs | 11 +++ kifu/gtk/src/main.rs | 28 +++--- kifu/gtk/src/ui/board.rs | 150 ++++++++++++++++--------------- kifu/gtk/src/ui/playing_field.rs | 29 ++++-- 4 files changed, 117 insertions(+), 101 deletions(-) diff --git a/kifu/gtk/src/lib.rs b/kifu/gtk/src/lib.rs index b0ef459..94c88d6 100644 --- a/kifu/gtk/src/lib.rs +++ b/kifu/gtk/src/lib.rs @@ -19,3 +19,14 @@ impl CoreApi { }); } } + +pub fn perftrace(trace_name: &str, f: F) -> A +where + F: FnOnce() -> A, +{ + let start = std::time::Instant::now(); + let result = f(); + let end = std::time::Instant::now(); + println!("[Trace: {}] {:?}", trace_name, end - start); + return result; +} diff --git a/kifu/gtk/src/main.rs b/kifu/gtk/src/main.rs index b9a21a4..3e34e3e 100644 --- a/kifu/gtk/src/main.rs +++ b/kifu/gtk/src/main.rs @@ -1,43 +1,35 @@ use gtk::prelude::*; use kifu_core::{CoreApp, CoreRequest, CoreResponse}; use kifu_gtk::{ + perftrace, ui::{NewGame, PlayingField}, CoreApi, }; use std::sync::{Arc, RwLock}; -fn perftrace(trace_name: &str, f: F) -> A -where - F: FnOnce() -> A, -{ - let start = std::time::Instant::now(); - let result = f(); - let end = std::time::Instant::now(); - println!("[Trace: {}] {:?}", trace_name, end - start); - return result; -} - fn handle_response(api: CoreApi, window: gtk::ApplicationWindow, message: CoreResponse) { let playing_field = Arc::new(RwLock::new(None)); match message { - CoreResponse::NewGameView(view) => { + CoreResponse::NewGameView(view) => perftrace("NewGameView", || { let api = api.clone(); let new_game = NewGame::new(api, view); window.set_child(Some(&new_game)); - } - CoreResponse::PlayingFieldView(view) => { + }), + CoreResponse::PlayingFieldView(view) => perftrace("PlayingFieldView", || { let api = api.clone(); 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); + perftrace("creating a new playing field", || { + 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)); } - } + }), } } diff --git a/kifu/gtk/src/ui/board.rs b/kifu/gtk/src/ui/board.rs index 2940066..fa3e67d 100644 --- a/kifu/gtk/src/ui/board.rs +++ b/kifu/gtk/src/ui/board.rs @@ -1,4 +1,4 @@ -use crate::CoreApi; +use crate::{perftrace, CoreApi}; use gio::resources_lookup_data; use glib::Object; use gtk::{ @@ -83,86 +83,88 @@ 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(); + perftrace("render drawing area", || { + let render_start = std::time::Instant::now(); + let board = board.borrow(); - match background { - Ok(Some(ref background)) => { - context.set_source_pixbuf(&background, 0., 0.); - context.paint().expect("paint should succeed"); - } - Ok(None) | Err(_) => context.set_source_rgb(0.7, 0.7, 0.7), - }; - let _ = context.paint(); + match background { + Ok(Some(ref background)) => { + context.set_source_pixbuf(&background, 0., 0.); + context.paint().expect("paint should succeed"); + } + Ok(None) | Err(_) => context.set_source_rgb(0.7, 0.7, 0.7), + }; + let _ = context.paint(); - context.set_source_rgb(0.1, 0.1, 0.1); - context.set_line_width(2.); - 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); + context.set_source_rgb(0.1, 0.1, 0.1); + context.set_line_width(2.); + 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: MARGIN as f64, - y_offset: MARGIN as f64, - hspace_between, - vspace_between, - }; + let pen = Pen { + x_offset: MARGIN as f64, + y_offset: MARGIN as f64, + hspace_between, + vspace_between, + }; - (0..board.size.width).for_each(|col| { - 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( - 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(); - }); - - context.set_source_rgb(0.1, 0.1, 0.0); - vec![3, 9, 15].into_iter().for_each(|col| { - vec![3, 9, 15].into_iter().for_each(|row| { - pen.star_point(context, col, row); + (0..board.size.width).for_each(|col| { + 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( + 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(); }); - }); - (0..19).for_each(|col| { - (0..19).for_each(|row| { - match board.stone(row, col) { - IntersectionElement::Filled(stone) => { - pen.stone(&context, row, col, stone.color, stone.liberties); - } + context.set_source_rgb(0.1, 0.1, 0.0); + vec![3, 9, 15].into_iter().for_each(|col| { + vec![3, 9, 15].into_iter().for_each(|row| { + pen.star_point(context, col, row); + }); + }); + + (0..19).for_each(|col| { + (0..19).for_each(|row| { + match board.stone(row, col) { + IntersectionElement::Filled(stone) => { + pen.stone(&context, row, col, stone.color, stone.liberties); + } + _ => {} + }; + }) + }); + + let cursor = cursor_location.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 cursor = cursor_location.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); + }, + } + 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/gtk/src/ui/playing_field.rs b/kifu/gtk/src/ui/playing_field.rs index afcb93d..74e4087 100644 --- a/kifu/gtk/src/ui/playing_field.rs +++ b/kifu/gtk/src/ui/playing_field.rs @@ -1,8 +1,17 @@ -use crate::ui::{Board, Chat, PlayerCard}; -use crate::CoreApi; +use crate::{ + perftrace, + ui::{Board, Chat, PlayerCard}, + CoreApi, +}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use kifu_core::ui::PlayingFieldView; +use kifu_core::{ + ui::{ + BoardElement, ChatElement, IntersectionElement, PlayerCardElement, PlayingFieldView, + StoneElement, TextFieldElement, + }, + Color, CoreApp, Size, +}; use std::{cell::RefCell, rc::Rc}; pub struct PlayingFieldPrivate { @@ -69,10 +78,12 @@ impl PlayingField { } 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); - }); + perftrace("update_view", || { + self.imp().board.borrow().as_ref().map(|board| { + board.set_board(view.board); + board.set_current_player(view.current_player); + }); + }) } } @@ -82,8 +93,8 @@ pub fn playing_field_view() -> PlayingFieldView { 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(4, 4) = IntersectionElement::Filled(StoneElement::new(Color::White, None)); + *board.stone_mut(15, 15) = IntersectionElement::Filled(StoneElement::new(Color::Black, None)); *board.stone_mut(18, 18) = IntersectionElement::Unplayable; let player_card_black = PlayerCardElement { color: Color::Black, -- 2.44.1 From 32ed1a24649a9546f682f9d105cf973cdc59f047 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 14 Jun 2023 23:48:09 -0400 Subject: [PATCH 3/7] Add a rule for starting the screenplay --- kifu/gtk/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kifu/gtk/Makefile b/kifu/gtk/Makefile index 2297add..be968cd 100644 --- a/kifu/gtk/Makefile +++ b/kifu/gtk/Makefile @@ -3,3 +3,7 @@ release: dev: cargo watch -x 'run --bin kifu-gtk' + +screenplay: + cargo watch -x 'run --bin screenplay --features="screenplay"' + -- 2.44.1 From d0db274110dacb80d5709d38b508fb0da7612992 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Wed, 14 Jun 2023 23:49:03 -0400 Subject: [PATCH 4/7] Create a launch screen that shows player name and rank --- kifu/core/src/api.rs | 40 ++++++++++--- kifu/core/src/lib.rs | 2 +- kifu/core/src/types.rs | 2 +- kifu/core/src/ui/mod.rs | 2 +- kifu/core/src/ui/new_game.rs | 58 ++++++++++++------- kifu/core/src/ui/types.rs | 51 +++++++++------- kifu/gtk/src/ui/new_game.rs | 109 +++++++++++++++++++++++++---------- 7 files changed, 182 insertions(+), 82 deletions(-) diff --git a/kifu/core/src/api.rs b/kifu/core/src/api.rs index 401cbb9..efcb483 100644 --- a/kifu/core/src/api.rs +++ b/kifu/core/src/api.rs @@ -1,18 +1,20 @@ -use crate::types::AppState; -use crate::ui::{new_game, playing_field, NewGameView, PlayingFieldView}; +use crate::{ + types::{AppState, Rank}, + ui::{new_game, playing_field, NewGameView, PlayingFieldView}, +}; use serde::{Deserialize, Serialize}; use std::sync::{Arc, RwLock}; use typeshare::typeshare; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[typeshare] #[serde(tag = "type", content = "content")] pub enum CoreRequest { - // CreateGameRequest(CreateGameRequest), + CreateGame(CreateGameRequest), LaunchScreen, NewGame, PlayingField, - PlayStoneRequest(PlayStoneRequest), + PlayStone(PlayStoneRequest), StartGame, } @@ -23,6 +25,26 @@ pub struct PlayStoneRequest { pub row: u8, } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[typeshare] +pub struct CreateGameRequest { + black_player: PlayerInfoRequest, + white_player: PlayerInfoRequest, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[typeshare] +pub enum PlayerInfoRequest { + Hotseat(HotseatPlayerRequest), +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[typeshare] +pub struct HotseatPlayerRequest { + name: Option, + rank: Option, +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[typeshare] #[serde(tag = "type", content = "content")] @@ -59,7 +81,11 @@ impl CoreApp { For the initial version, I want only to show the game creation screen. Then I will backtrack record application state so that the only decisions can be made. } */ - // CoreRequest::LaunchScreen => CoreResponse::NewGameView(new_game()), + CoreRequest::CreateGame(_) => { + let app_state = self.state.write().unwrap(); + let game = app_state.game.as_ref().unwrap(); + CoreResponse::PlayingFieldView(playing_field(game)) + } CoreRequest::LaunchScreen => CoreResponse::NewGameView(new_game()), CoreRequest::NewGame => CoreResponse::NewGameView(new_game()), CoreRequest::PlayingField => { @@ -67,7 +93,7 @@ impl CoreApp { let game = app_state.game.as_ref().unwrap(); CoreResponse::PlayingFieldView(playing_field(game)) } - CoreRequest::PlayStoneRequest(request) => { + CoreRequest::PlayStone(request) => { let mut app_state = self.state.write().unwrap(); app_state.place_stone(request); diff --git a/kifu/core/src/lib.rs b/kifu/core/src/lib.rs index bcdd2a0..386dd0a 100644 --- a/kifu/core/src/lib.rs +++ b/kifu/core/src/lib.rs @@ -2,7 +2,7 @@ mod api; pub use api::{CoreApp, CoreRequest, CoreResponse}; mod types; -pub use types::{BoardError, Color, Size}; +pub use types::{BoardError, Color, Rank, Size}; pub mod ui; mod board; diff --git a/kifu/core/src/types.rs b/kifu/core/src/types.rs index 5747c07..aafabf7 100644 --- a/kifu/core/src/types.rs +++ b/kifu/core/src/types.rs @@ -65,7 +65,7 @@ impl AppState { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[typeshare] pub enum Rank { Kyu(u8), diff --git a/kifu/core/src/ui/mod.rs b/kifu/core/src/ui/mod.rs index f7f43e1..caf1a8d 100644 --- a/kifu/core/src/ui/mod.rs +++ b/kifu/core/src/ui/mod.rs @@ -5,7 +5,7 @@ pub use playing_field::{playing_field, PlayingFieldView}; // pub use launch_screen::{launch_screen, LaunchScreenView}; mod new_game; -pub use new_game::{new_game, Menu, NewGameView, Player}; +pub use new_game::{new_game, HotseatPlayerElement, Menu, NewGameView, PlayerElement}; mod types; pub use types::{ diff --git a/kifu/core/src/ui/new_game.rs b/kifu/core/src/ui/new_game.rs index e95cf70..3288530 100644 --- a/kifu/core/src/ui/new_game.rs +++ b/kifu/core/src/ui/new_game.rs @@ -1,7 +1,20 @@ -use crate::types::Rank; use serde::{Deserialize, Serialize}; use typeshare::typeshare; +fn rank_strings() -> Vec { + vec![ + "", "30 kyu", "29 kyu", "28 kyu", "27 kyu", "26 kyu", "25 kyu", "24 kyu", "23 kyu", + "22 kyu", "21 kyu", "20 kyu", "19 kyu", "18 kyu", "17 kyu", "16 kyu", "15 kyu", "14 kyu", + "13 kyu", "12 kyu", "11 kyu", "10 kyu", "9 kyu", "8 kyu", "7 kyu", "6 kyu", "5 kyu", + "4 kyu", "3 kyu", "2 kyu", "1 kyu", "1 dan", "2 dan", "3 dan", "4 dan", "5 dan", "6 dan", + "7 dan", "8 dan", "9 dan", "1 pro", "2 pro", "3 pro", "4 pro", "5 pro", "6 pro", "7 pro", + "8 pro", "9 pro", + ] + .into_iter() + .map(|s| s.to_owned()) + .collect() +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[typeshare] pub struct Menu { @@ -20,48 +33,51 @@ impl Menu { #[derive(Clone, Debug, Serialize, Deserialize)] #[typeshare] -pub enum Player { - Hotseat(HotseatPlayer), - Remote(RemotePlayer), - Bot(BotPlayer), +pub enum PlayerElement { + Hotseat(HotseatPlayerElement), + // Remote(RemotePlayerElement), + // Bot(BotPlayerElement), } -impl Default for Player { - fn default() -> Player { - Player::Hotseat(HotseatPlayer::default()) +impl Default for PlayerElement { + fn default() -> PlayerElement { + PlayerElement::Hotseat(HotseatPlayerElement::default()) } } #[derive(Clone, Debug, Serialize, Deserialize, Default)] #[typeshare] -pub struct HotseatPlayer { - pub name: Option, - pub rank: Option, +pub struct HotseatPlayerElement { + pub placeholder: Option, + pub default_rank: Option, + pub ranks: Vec, } #[derive(Clone, Debug, Serialize, Deserialize)] #[typeshare] -pub struct RemotePlayer {} +pub struct RemotePlayerElement {} #[derive(Clone, Debug, Serialize, Deserialize)] #[typeshare] -pub struct BotPlayer {} +pub struct BotPlayerElement {} #[derive(Clone, Debug, Serialize, Deserialize)] #[typeshare] pub struct NewGameView { - pub black_player: Player, - pub white_player: Player, + pub black_player: PlayerElement, + pub white_player: PlayerElement, } pub fn new_game() -> NewGameView { - let black_player = Player::Hotseat(HotseatPlayer { - name: Some("black_player".to_owned()), - ..Default::default() + let black_player = PlayerElement::Hotseat(HotseatPlayerElement { + placeholder: Some("black player".to_owned()), + default_rank: None, + ranks: rank_strings(), }); - let white_player = Player::Hotseat(HotseatPlayer { - name: Some("white_player".to_owned()), - ..Default::default() + let white_player = PlayerElement::Hotseat(HotseatPlayerElement { + placeholder: Some("white player".to_owned()), + default_rank: None, + ranks: rank_strings(), }); NewGameView { black_player, diff --git a/kifu/core/src/ui/types.rs b/kifu/core/src/ui/types.rs index c12c416..1454a31 100644 --- a/kifu/core/src/ui/types.rs +++ b/kifu/core/src/ui/types.rs @@ -31,7 +31,7 @@ impl StoneElement { } } -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[typeshare] #[serde(tag = "type", content = "content")] pub enum IntersectionElement { @@ -53,9 +53,10 @@ impl BoardElement { .map(|row| { (0..size.width) .map(|column| { - IntersectionElement::Empty(CoreRequest::PlayStoneRequest( - PlayStoneRequest { column, row }, - )) + IntersectionElement::Empty(CoreRequest::PlayStone(PlayStoneRequest { + column, + row, + })) }) .collect::>() }) @@ -66,7 +67,12 @@ impl BoardElement { pub fn stone(&self, column: u8, row: u8) -> IntersectionElement { let addr = self.addr(column, row); - self.spaces[addr] + self.spaces[addr].clone() + } + + pub fn stone_mut(&mut self, column: u8, row: u8) -> &mut IntersectionElement { + let addr = self.addr(column, row); + &mut self.spaces[addr] } fn addr(&self, column: u8, row: u8) -> usize { @@ -76,23 +82,24 @@ impl BoardElement { 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(&Coordinate { column, row }) { - Some(color) => IntersectionElement::Filled(StoneElement { - jitter: Jitter { x: 0, y: 0 }, - liberties: None, - color, - }), - None => IntersectionElement::Empty(CoreRequest::PlayStoneRequest( - PlayStoneRequest { column, row }, - )), - }) - .collect::>() - }) - .collect::>>() - .concat(); + let spaces: Vec = + (0..board.size.height) + .map(|row| { + (0..board.size.width) + .map(|column| match board.stone(&Coordinate { column, row }) { + Some(color) => IntersectionElement::Filled(StoneElement { + jitter: Jitter { x: 0, y: 0 }, + liberties: None, + color, + }), + None => IntersectionElement::Empty(CoreRequest::PlayStone( + PlayStoneRequest { column, row }, + )), + }) + .collect::>() + }) + .collect::>>() + .concat(); Self { size: board.size, spaces, diff --git a/kifu/gtk/src/ui/new_game.rs b/kifu/gtk/src/ui/new_game.rs index d6dcaf4..950bab1 100644 --- a/kifu/gtk/src/ui/new_game.rs +++ b/kifu/gtk/src/ui/new_game.rs @@ -1,19 +1,74 @@ use crate::CoreApi; -use glib::Object; -use gtk::{prelude::*, subclass::prelude::*}; -use kifu_core::ui::{NewGameView, Player}; -use std::{cell::RefCell, rc::Rc}; +use glib::{subclass, Object, ParamSpec, Properties, Value}; +use gtk::{glib, prelude::*, subclass::prelude::*}; +use kifu_core::ui::{NewGameView, PlayerElement}; +use std::{cell::Cell, cell::RefCell, rc::Rc}; + +struct PlayerDataEntryPrivate { + placeholder: gtk::Text, + rank: gtk::DropDown, +} + +impl Default for PlayerDataEntryPrivate { + fn default() -> Self { + let rank = gtk::DropDown::builder().build(); + Self { + placeholder: gtk::Text::builder().build(), + rank, + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for PlayerDataEntryPrivate { + const NAME: &'static str = "PlayerDataEntry"; + type Type = PlayerDataEntry; + type ParentType = gtk::Box; +} + +impl ObjectImpl for PlayerDataEntryPrivate {} +impl WidgetImpl for PlayerDataEntryPrivate {} +impl BoxImpl for PlayerDataEntryPrivate {} + +glib::wrapper! { + struct PlayerDataEntry(ObjectSubclass) @extends gtk::Box, gtk::Widget; +} + +impl PlayerDataEntry { + pub fn new(element: PlayerElement) -> PlayerDataEntry { + let s: Self = Object::builder().build(); + + let rank_model = gio::ListStore::new(gtk::StringObject::static_type()); + s.imp().rank.set_model(Some(&rank_model)); + + match element { + PlayerElement::Hotseat(player) => { + if let Some(placeholder) = player.placeholder { + s.imp().placeholder.set_placeholder_text(Some(&placeholder)); + } + player.ranks.iter().for_each(|rank| rank_model.append(>k::StringObject::new(rank))); + } + // PlayerElement::Remote(_) => s.imp().placeholder.set_text("remote player"), + // PlayerElement::Bot(_) => s.imp().placeholder.set_text("bot player"), + } + + s.append(&s.imp().placeholder); + s.append(&s.imp().rank); + + s + } +} pub struct NewGamePrivate { - black_player: gtk::Label, - white_player: gtk::Label, + black_player: Rc>>, + white_player: Rc>>, } impl Default for NewGamePrivate { fn default() -> Self { Self { - black_player: gtk::Label::new(None), - white_player: gtk::Label::new(None), + black_player: Rc::new(RefCell::new(None)), + white_player: Rc::new(RefCell::new(None)), } } } @@ -34,30 +89,26 @@ glib::wrapper! { } impl NewGame { - pub fn new(_api: CoreApi, view: NewGameView) -> NewGame { + pub fn new(api: CoreApi, view: NewGameView) -> NewGame { let s: Self = Object::builder().build(); - match view.black_player { - Player::Hotseat(player) => { - if let Some(name) = player.name { - s.imp().black_player.set_text(name.as_ref()); - } - } - Player::Remote(_) => s.imp().black_player.set_text("remote player"), - Player::Bot(_) => s.imp().black_player.set_text("bot player"), - } - s.attach(&s.imp().black_player, 1, 1, 1, 1); + let black_player = PlayerDataEntry::new(view.black_player); + s.attach(&black_player, 1, 1, 1, 1); + *s.imp().black_player.borrow_mut() = Some(black_player); - match view.white_player { - Player::Hotseat(player) => { - if let Some(name) = player.name { - s.imp().white_player.set_text(name.as_ref()); - } - } - Player::Remote(_) => s.imp().black_player.set_text("remote player"), - Player::Bot(_) => s.imp().black_player.set_text("bot player"), - } - s.attach(&s.imp().white_player, 2, 1, 1, 1); + let white_player = PlayerDataEntry::new(view.white_player); + s.attach(&white_player, 2, 1, 1, 1); + *s.imp().white_player.borrow_mut() = Some(white_player); + + let new_game_button = gtk::Button::builder().label("Start Game").build(); + s.attach(&new_game_button, 2, 2, 1, 1); + + /* + new_game_button.connect_clicked(move |_| { + api.dispatch(CoreRequest::CreatGameRequest(CreateGameRequest { + })); + }); + */ s } -- 2.44.1 From ebb7062041af2581219eae2f95cfcbeafbe6ac30 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 15 Jun 2023 00:09:50 -0400 Subject: [PATCH 5/7] Go back to creating a new game --- kifu/core/src/api.rs | 8 ++++---- kifu/core/src/lib.rs | 4 +++- kifu/core/src/ui/elements/action.rs | 10 ++++++++++ kifu/core/src/ui/elements/menu.rs | 18 ++++++++++++++++++ kifu/core/src/ui/elements/mod.rs | 2 ++ kifu/core/src/ui/mod.rs | 5 ++++- kifu/core/src/ui/new_game.rs | 23 +++++++---------------- kifu/gtk/src/ui/new_game.rs | 19 ++++++++++++++----- 8 files changed, 62 insertions(+), 27 deletions(-) create mode 100644 kifu/core/src/ui/elements/action.rs create mode 100644 kifu/core/src/ui/elements/menu.rs create mode 100644 kifu/core/src/ui/elements/mod.rs diff --git a/kifu/core/src/api.rs b/kifu/core/src/api.rs index efcb483..864953c 100644 --- a/kifu/core/src/api.rs +++ b/kifu/core/src/api.rs @@ -28,8 +28,8 @@ pub struct PlayStoneRequest { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[typeshare] pub struct CreateGameRequest { - black_player: PlayerInfoRequest, - white_player: PlayerInfoRequest, + pub black_player: PlayerInfoRequest, + pub white_player: PlayerInfoRequest, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -41,8 +41,8 @@ pub enum PlayerInfoRequest { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[typeshare] pub struct HotseatPlayerRequest { - name: Option, - rank: Option, + pub name: Option, + pub rank: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/kifu/core/src/lib.rs b/kifu/core/src/lib.rs index 386dd0a..fe0d321 100644 --- a/kifu/core/src/lib.rs +++ b/kifu/core/src/lib.rs @@ -1,5 +1,7 @@ mod api; -pub use api::{CoreApp, CoreRequest, CoreResponse}; +pub use api::{ + CoreApp, CoreRequest, CoreResponse, CreateGameRequest, HotseatPlayerRequest, PlayerInfoRequest, +}; mod types; pub use types::{BoardError, Color, Rank, Size}; diff --git a/kifu/core/src/ui/elements/action.rs b/kifu/core/src/ui/elements/action.rs new file mode 100644 index 0000000..69c5514 --- /dev/null +++ b/kifu/core/src/ui/elements/action.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[typeshare] +pub struct Action { + pub id: String, + pub label: String, + pub action: A, +} diff --git a/kifu/core/src/ui/elements/menu.rs b/kifu/core/src/ui/elements/menu.rs new file mode 100644 index 0000000..25b06ed --- /dev/null +++ b/kifu/core/src/ui/elements/menu.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[typeshare] +pub struct Menu { + current: Option, + options: Vec, +} + +impl Menu { + pub fn new(options: Vec, current: Option) -> Menu { + if let Some(ref current) = current { + assert!(options.contains(current)); + } + Menu { current, options } + } +} diff --git a/kifu/core/src/ui/elements/mod.rs b/kifu/core/src/ui/elements/mod.rs new file mode 100644 index 0000000..c5aac34 --- /dev/null +++ b/kifu/core/src/ui/elements/mod.rs @@ -0,0 +1,2 @@ +pub mod action; +pub mod menu; diff --git a/kifu/core/src/ui/mod.rs b/kifu/core/src/ui/mod.rs index caf1a8d..6ae201e 100644 --- a/kifu/core/src/ui/mod.rs +++ b/kifu/core/src/ui/mod.rs @@ -1,3 +1,6 @@ +mod elements; +pub use elements::{action::Action, menu::Menu}; + mod playing_field; pub use playing_field::{playing_field, PlayingFieldView}; @@ -5,7 +8,7 @@ pub use playing_field::{playing_field, PlayingFieldView}; // pub use launch_screen::{launch_screen, LaunchScreenView}; mod new_game; -pub use new_game::{new_game, HotseatPlayerElement, Menu, NewGameView, PlayerElement}; +pub use new_game::{new_game, HotseatPlayerElement, NewGameView, PlayerElement}; mod types; pub use types::{ diff --git a/kifu/core/src/ui/new_game.rs b/kifu/core/src/ui/new_game.rs index 3288530..afe913a 100644 --- a/kifu/core/src/ui/new_game.rs +++ b/kifu/core/src/ui/new_game.rs @@ -1,3 +1,4 @@ +use crate::ui::Action; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,22 +16,6 @@ fn rank_strings() -> Vec { .collect() } -#[derive(Clone, Debug, Serialize, Deserialize)] -#[typeshare] -pub struct Menu { - current: Option, - options: Vec, -} - -impl Menu { - pub fn new(options: Vec, current: Option) -> Menu { - if let Some(ref current) = current { - assert!(options.contains(current)); - } - Menu { current, options } - } -} - #[derive(Clone, Debug, Serialize, Deserialize)] #[typeshare] pub enum PlayerElement { @@ -66,6 +51,7 @@ pub struct BotPlayerElement {} pub struct NewGameView { pub black_player: PlayerElement, pub white_player: PlayerElement, + pub start_game: Action<()>, } pub fn new_game() -> NewGameView { @@ -82,5 +68,10 @@ pub fn new_game() -> NewGameView { NewGameView { black_player, white_player, + start_game: Action { + id: "start-game-action".to_owned(), + label: "New Game".to_owned(), + action: (), + }, } } diff --git a/kifu/gtk/src/ui/new_game.rs b/kifu/gtk/src/ui/new_game.rs index 950bab1..df4dbe1 100644 --- a/kifu/gtk/src/ui/new_game.rs +++ b/kifu/gtk/src/ui/new_game.rs @@ -1,7 +1,10 @@ use crate::CoreApi; use glib::{subclass, Object, ParamSpec, Properties, Value}; use gtk::{glib, prelude::*, subclass::prelude::*}; -use kifu_core::ui::{NewGameView, PlayerElement}; +use kifu_core::{ + ui::{NewGameView, PlayerElement}, + CoreRequest, CreateGameRequest, HotseatPlayerRequest, PlayerInfoRequest, +}; use std::{cell::Cell, cell::RefCell, rc::Rc}; struct PlayerDataEntryPrivate { @@ -100,15 +103,21 @@ impl NewGame { s.attach(&white_player, 2, 1, 1, 1); *s.imp().white_player.borrow_mut() = Some(white_player); - let new_game_button = gtk::Button::builder().label("Start Game").build(); + let new_game_button = gtk::Button::builder().label(&view.start_game.label).build(); s.attach(&new_game_button, 2, 2, 1, 1); - /* new_game_button.connect_clicked(move |_| { - api.dispatch(CoreRequest::CreatGameRequest(CreateGameRequest { + api.dispatch(CoreRequest::CreateGame(CreateGameRequest { + black_player: PlayerInfoRequest::Hotseat(HotseatPlayerRequest { + name: None, + rank: None, + }), + white_player: PlayerInfoRequest::Hotseat(HotseatPlayerRequest { + name: None, + rank: None, + }), })); }); - */ s } -- 2.44.1 From 9266387833ed7258bdaf7a8a094ec3b799bd920c Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 15 Jun 2023 22:20:59 -0400 Subject: [PATCH 6/7] Communicate name and rank from the UI to the core --- kifu/core/src/api.rs | 38 ++++++++++++++---- kifu/core/src/types.rs | 14 +++---- kifu/core/src/ui/playing_field.rs | 20 +++++++--- kifu/gtk/src/ui/new_game.rs | 64 ++++++++++++++++++++++--------- 4 files changed, 97 insertions(+), 39 deletions(-) diff --git a/kifu/core/src/api.rs b/kifu/core/src/api.rs index 864953c..7e4e0d6 100644 --- a/kifu/core/src/api.rs +++ b/kifu/core/src/api.rs @@ -1,5 +1,5 @@ use crate::{ - types::{AppState, Rank}, + types::{AppState, GameState, Player, Rank}, ui::{new_game, playing_field, NewGameView, PlayingFieldView}, }; use serde::{Deserialize, Serialize}; @@ -41,8 +41,17 @@ pub enum PlayerInfoRequest { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[typeshare] pub struct HotseatPlayerRequest { - pub name: Option, - pub rank: Option, + pub name: String, + pub rank: Option, +} + +impl From for Player { + fn from(p: HotseatPlayerRequest) -> Self { + Self { + name: p.name, + rank: p.rank.and_then(|r| Rank::try_from(r.as_ref()).ok()), + } + } } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -81,10 +90,25 @@ impl CoreApp { For the initial version, I want only to show the game creation screen. Then I will backtrack record application state so that the only decisions can be made. } */ - CoreRequest::CreateGame(_) => { - let app_state = self.state.write().unwrap(); - let game = app_state.game.as_ref().unwrap(); - CoreResponse::PlayingFieldView(playing_field(game)) + CoreRequest::CreateGame(create_request) => { + let mut app_state = self.state.write().unwrap(); + let white_player = { + match create_request.white_player { + PlayerInfoRequest::Hotseat(request) => Player::from(request), + } + }; + let black_player = { + match create_request.black_player { + PlayerInfoRequest::Hotseat(request) => Player::from(request), + } + }; + app_state.game = Some(GameState { + white_player, + black_player, + ..GameState::new() + }); + let game_state = app_state.game.as_ref().unwrap(); + CoreResponse::PlayingFieldView(playing_field(game_state)) } CoreRequest::LaunchScreen => CoreResponse::NewGameView(new_game()), CoreRequest::NewGame => CoreResponse::NewGameView(new_game()), diff --git a/kifu/core/src/types.rs b/kifu/core/src/types.rs index aafabf7..fec9790 100644 --- a/kifu/core/src/types.rs +++ b/kifu/core/src/types.rs @@ -93,8 +93,8 @@ impl From for String { #[derive(Debug)] pub struct Player { - name: String, - rank: Rank, + pub name: String, + pub rank: Option, } #[derive(Debug)] @@ -113,19 +113,19 @@ pub struct GameState { } impl GameState { - fn new() -> GameState { + pub fn new() -> GameState { GameState { board: Board::new(), past_positions: vec![], conversation: vec![], current_player: Color::Black, white_player: Player { - name: "Savanni".to_owned(), - rank: Rank::Kyu(10), + name: "".to_owned(), + rank: None, }, black_player: Player { - name: "Opal".to_owned(), - rank: Rank::Kyu(10), + name: "".to_owned(), + rank: None, }, white_clock: Duration::from_secs(600), black_clock: Duration::from_secs(600), diff --git a/kifu/core/src/ui/playing_field.rs b/kifu/core/src/ui/playing_field.rs index 2e8592c..d5ed200 100644 --- a/kifu/core/src/ui/playing_field.rs +++ b/kifu/core/src/ui/playing_field.rs @@ -21,16 +21,24 @@ pub fn playing_field(game: &GameState) -> PlayingFieldView { let player_card_black = types::PlayerCardElement { color: Color::Black, - name: "Savanni".to_owned(), - rank: "10k".to_owned(), - clock: "24:53".to_owned(), + name: game.black_player.name.clone(), + rank: game + .black_player + .rank + .map(String::from) + .unwrap_or("".to_owned()), + clock: "".to_owned(), }; let player_card_white = types::PlayerCardElement { color: Color::White, - name: "Opal".to_owned(), - rank: "10k".to_owned(), - clock: "25:00".to_owned(), + name: game.white_player.name.clone(), + rank: game + .black_player + .rank + .map(String::from) + .unwrap_or("".to_owned()), + clock: "".to_owned(), }; let chat = types::ChatElement { diff --git a/kifu/gtk/src/ui/new_game.rs b/kifu/gtk/src/ui/new_game.rs index df4dbe1..d037ef2 100644 --- a/kifu/gtk/src/ui/new_game.rs +++ b/kifu/gtk/src/ui/new_game.rs @@ -1,14 +1,14 @@ use crate::CoreApi; -use glib::{subclass, Object, ParamSpec, Properties, Value}; +use glib::Object; use gtk::{glib, prelude::*, subclass::prelude::*}; use kifu_core::{ ui::{NewGameView, PlayerElement}, CoreRequest, CreateGameRequest, HotseatPlayerRequest, PlayerInfoRequest, }; -use std::{cell::Cell, cell::RefCell, rc::Rc}; +use std::{cell::RefCell, rc::Rc}; struct PlayerDataEntryPrivate { - placeholder: gtk::Text, + name: gtk::Text, rank: gtk::DropDown, } @@ -16,7 +16,7 @@ impl Default for PlayerDataEntryPrivate { fn default() -> Self { let rank = gtk::DropDown::builder().build(); Self { - placeholder: gtk::Text::builder().build(), + name: gtk::Text::builder().build(), rank, } } @@ -47,7 +47,7 @@ impl PlayerDataEntry { match element { PlayerElement::Hotseat(player) => { if let Some(placeholder) = player.placeholder { - s.imp().placeholder.set_placeholder_text(Some(&placeholder)); + s.imp().name.set_placeholder_text(Some(&placeholder)); } player.ranks.iter().for_each(|rank| rank_model.append(>k::StringObject::new(rank))); } @@ -55,11 +55,31 @@ impl PlayerDataEntry { // PlayerElement::Bot(_) => s.imp().placeholder.set_text("bot player"), } - s.append(&s.imp().placeholder); + s.append(&s.imp().name); s.append(&s.imp().rank); s } + + pub fn text(&self) -> String { + let name = self.imp().name.buffer().text().to_string(); + if name.is_empty() { + self.imp() + .name + .placeholder_text() + .map(|s| s.to_string()) + .unwrap_or("".to_owned()) + } else { + name + } + } + + pub fn rank(&self) -> Option { + self.imp().rank.selected_item().and_then(|obj| { + let str_obj = obj.downcast::().ok()?; + Some(str_obj.string().clone().to_string()) + }) + } } pub struct NewGamePrivate { @@ -97,28 +117,34 @@ impl NewGame { let black_player = PlayerDataEntry::new(view.black_player); s.attach(&black_player, 1, 1, 1, 1); - *s.imp().black_player.borrow_mut() = Some(black_player); + *s.imp().black_player.borrow_mut() = Some(black_player.clone()); let white_player = PlayerDataEntry::new(view.white_player); s.attach(&white_player, 2, 1, 1, 1); - *s.imp().white_player.borrow_mut() = Some(white_player); + *s.imp().white_player.borrow_mut() = Some(white_player.clone()); let new_game_button = gtk::Button::builder().label(&view.start_game.label).build(); s.attach(&new_game_button, 2, 2, 1, 1); - new_game_button.connect_clicked(move |_| { - api.dispatch(CoreRequest::CreateGame(CreateGameRequest { - black_player: PlayerInfoRequest::Hotseat(HotseatPlayerRequest { - name: None, - rank: None, - }), - white_player: PlayerInfoRequest::Hotseat(HotseatPlayerRequest { - name: None, - rank: None, - }), - })); + new_game_button.connect_clicked({ + move |_| { + let black_player = black_player.clone(); + let white_player = white_player.clone(); + + api.dispatch(CoreRequest::CreateGame(CreateGameRequest { + black_player: player_info(black_player.clone()), + white_player: player_info(white_player.clone()), + })); + } }); s } } + +fn player_info(player: PlayerDataEntry) -> PlayerInfoRequest { + PlayerInfoRequest::Hotseat(HotseatPlayerRequest { + name: player.text(), + rank: player.rank(), + }) +} -- 2.44.1 From b15658dd0c7dbb116f418fe15feb8e252fb4c732 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 16 Jun 2023 09:03:17 -0400 Subject: [PATCH 7/7] Clean up warnings --- Makefile | 6 +++--- kifu/core/src/types.rs | 2 +- kifu/gtk/src/ui/playing_field.rs | 8 +------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 940003b..ef03c40 100644 --- a/Makefile +++ b/Makefile @@ -36,13 +36,13 @@ ifc-test: cd ifc && make test kifu-core/dev: - cd kifu/kifu-core && make test + cd kifu/core && make test kifu-core/test: - cd kifu/kifu-core && make test + cd kifu/core && make test kifu-core/test-oneshot: - cd kifu/kifu-core && make test-oneshot + cd kifu/core && make test-oneshot kifu-gtk: cd kifu/gtk && make release diff --git a/kifu/core/src/types.rs b/kifu/core/src/types.rs index fec9790..3249ea4 100644 --- a/kifu/core/src/types.rs +++ b/kifu/core/src/types.rs @@ -55,7 +55,7 @@ impl AppState { pub fn place_stone(&mut self, req: PlayStoneRequest) { match self.game { Some(ref mut game) => { - game.place_stone(Coordinate { + let _ = game.place_stone(Coordinate { column: req.column, row: req.row, }); diff --git a/kifu/gtk/src/ui/playing_field.rs b/kifu/gtk/src/ui/playing_field.rs index 74e4087..dce713e 100644 --- a/kifu/gtk/src/ui/playing_field.rs +++ b/kifu/gtk/src/ui/playing_field.rs @@ -5,13 +5,7 @@ use crate::{ }; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use kifu_core::{ - ui::{ - BoardElement, ChatElement, IntersectionElement, PlayerCardElement, PlayingFieldView, - StoneElement, TextFieldElement, - }, - Color, CoreApp, Size, -}; +use kifu_core::ui::PlayingFieldView; use std::{cell::RefCell, rc::Rc}; pub struct PlayingFieldPrivate { -- 2.44.1