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 }