From ef045117befcd13f38e81fb5df2effa2cee9aabe Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 26 May 2023 00:16:40 -0400 Subject: [PATCH] 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 + } +}