Create a launch screen that shows player name and rank

This commit is contained in:
Savanni D'Gerinel 2023-06-14 23:49:03 -04:00
parent 32ed1a2464
commit d0db274110
7 changed files with 182 additions and 82 deletions

View File

@ -1,18 +1,20 @@
use crate::types::AppState; use crate::{
use crate::ui::{new_game, playing_field, NewGameView, PlayingFieldView}; types::{AppState, Rank},
ui::{new_game, playing_field, NewGameView, PlayingFieldView},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use typeshare::typeshare; use typeshare::typeshare;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare] #[typeshare]
#[serde(tag = "type", content = "content")] #[serde(tag = "type", content = "content")]
pub enum CoreRequest { pub enum CoreRequest {
// CreateGameRequest(CreateGameRequest), CreateGame(CreateGameRequest),
LaunchScreen, LaunchScreen,
NewGame, NewGame,
PlayingField, PlayingField,
PlayStoneRequest(PlayStoneRequest), PlayStone(PlayStoneRequest),
StartGame, StartGame,
} }
@ -23,6 +25,26 @@ pub struct PlayStoneRequest {
pub row: u8, 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<String>,
rank: Option<Rank>,
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare] #[typeshare]
#[serde(tag = "type", content = "content")] #[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. 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::LaunchScreen => CoreResponse::NewGameView(new_game()),
CoreRequest::NewGame => CoreResponse::NewGameView(new_game()), CoreRequest::NewGame => CoreResponse::NewGameView(new_game()),
CoreRequest::PlayingField => { CoreRequest::PlayingField => {
@ -67,7 +93,7 @@ impl CoreApp {
let game = app_state.game.as_ref().unwrap(); let game = app_state.game.as_ref().unwrap();
CoreResponse::PlayingFieldView(playing_field(game)) CoreResponse::PlayingFieldView(playing_field(game))
} }
CoreRequest::PlayStoneRequest(request) => { CoreRequest::PlayStone(request) => {
let mut app_state = self.state.write().unwrap(); let mut app_state = self.state.write().unwrap();
app_state.place_stone(request); app_state.place_stone(request);

View File

@ -2,7 +2,7 @@ mod api;
pub use api::{CoreApp, CoreRequest, CoreResponse}; pub use api::{CoreApp, CoreRequest, CoreResponse};
mod types; mod types;
pub use types::{BoardError, Color, Size}; pub use types::{BoardError, Color, Rank, Size};
pub mod ui; pub mod ui;
mod board; mod board;

View File

@ -65,7 +65,7 @@ impl AppState {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare] #[typeshare]
pub enum Rank { pub enum Rank {
Kyu(u8), Kyu(u8),

View File

@ -5,7 +5,7 @@ pub use playing_field::{playing_field, PlayingFieldView};
// pub use launch_screen::{launch_screen, LaunchScreenView}; // pub use launch_screen::{launch_screen, LaunchScreenView};
mod new_game; mod new_game;
pub use new_game::{new_game, Menu, NewGameView, Player}; pub use new_game::{new_game, HotseatPlayerElement, Menu, NewGameView, PlayerElement};
mod types; mod types;
pub use types::{ pub use types::{

View File

@ -1,7 +1,20 @@
use crate::types::Rank;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typeshare::typeshare; use typeshare::typeshare;
fn rank_strings() -> Vec<String> {
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)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare] #[typeshare]
pub struct Menu<T: PartialEq + Eq> { pub struct Menu<T: PartialEq + Eq> {
@ -20,48 +33,51 @@ impl<T: PartialEq + Eq> Menu<T> {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare] #[typeshare]
pub enum Player { pub enum PlayerElement {
Hotseat(HotseatPlayer), Hotseat(HotseatPlayerElement),
Remote(RemotePlayer), // Remote(RemotePlayerElement),
Bot(BotPlayer), // Bot(BotPlayerElement),
} }
impl Default for Player { impl Default for PlayerElement {
fn default() -> Player { fn default() -> PlayerElement {
Player::Hotseat(HotseatPlayer::default()) PlayerElement::Hotseat(HotseatPlayerElement::default())
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize, Default)] #[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[typeshare] #[typeshare]
pub struct HotseatPlayer { pub struct HotseatPlayerElement {
pub name: Option<String>, pub placeholder: Option<String>,
pub rank: Option<Rank>, pub default_rank: Option<String>,
pub ranks: Vec<String>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare] #[typeshare]
pub struct RemotePlayer {} pub struct RemotePlayerElement {}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare] #[typeshare]
pub struct BotPlayer {} pub struct BotPlayerElement {}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare] #[typeshare]
pub struct NewGameView { pub struct NewGameView {
pub black_player: Player, pub black_player: PlayerElement,
pub white_player: Player, pub white_player: PlayerElement,
} }
pub fn new_game() -> NewGameView { pub fn new_game() -> NewGameView {
let black_player = Player::Hotseat(HotseatPlayer { let black_player = PlayerElement::Hotseat(HotseatPlayerElement {
name: Some("black_player".to_owned()), placeholder: Some("black player".to_owned()),
..Default::default() default_rank: None,
ranks: rank_strings(),
}); });
let white_player = Player::Hotseat(HotseatPlayer { let white_player = PlayerElement::Hotseat(HotseatPlayerElement {
name: Some("white_player".to_owned()), placeholder: Some("white player".to_owned()),
..Default::default() default_rank: None,
ranks: rank_strings(),
}); });
NewGameView { NewGameView {
black_player, black_player,

View File

@ -31,7 +31,7 @@ impl StoneElement {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[typeshare] #[typeshare]
#[serde(tag = "type", content = "content")] #[serde(tag = "type", content = "content")]
pub enum IntersectionElement { pub enum IntersectionElement {
@ -53,9 +53,10 @@ impl BoardElement {
.map(|row| { .map(|row| {
(0..size.width) (0..size.width)
.map(|column| { .map(|column| {
IntersectionElement::Empty(CoreRequest::PlayStoneRequest( IntersectionElement::Empty(CoreRequest::PlayStone(PlayStoneRequest {
PlayStoneRequest { column, row }, column,
)) row,
}))
}) })
.collect::<Vec<IntersectionElement>>() .collect::<Vec<IntersectionElement>>()
}) })
@ -66,7 +67,12 @@ impl BoardElement {
pub fn stone(&self, column: u8, row: u8) -> IntersectionElement { pub fn stone(&self, column: u8, row: u8) -> IntersectionElement {
let addr = self.addr(column, row); 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 { fn addr(&self, column: u8, row: u8) -> usize {
@ -76,23 +82,24 @@ impl BoardElement {
impl From<&Board> for BoardElement { impl From<&Board> for BoardElement {
fn from(board: &Board) -> Self { fn from(board: &Board) -> Self {
let spaces: Vec<IntersectionElement> = (0..board.size.height) let spaces: Vec<IntersectionElement> =
.map(|row| { (0..board.size.height)
(0..board.size.width) .map(|row| {
.map(|column| match board.stone(&Coordinate { column, row }) { (0..board.size.width)
Some(color) => IntersectionElement::Filled(StoneElement { .map(|column| match board.stone(&Coordinate { column, row }) {
jitter: Jitter { x: 0, y: 0 }, Some(color) => IntersectionElement::Filled(StoneElement {
liberties: None, jitter: Jitter { x: 0, y: 0 },
color, liberties: None,
}), color,
None => IntersectionElement::Empty(CoreRequest::PlayStoneRequest( }),
PlayStoneRequest { column, row }, None => IntersectionElement::Empty(CoreRequest::PlayStone(
)), PlayStoneRequest { column, row },
}) )),
.collect::<Vec<IntersectionElement>>() })
}) .collect::<Vec<IntersectionElement>>()
.collect::<Vec<Vec<IntersectionElement>>>() })
.concat(); .collect::<Vec<Vec<IntersectionElement>>>()
.concat();
Self { Self {
size: board.size, size: board.size,
spaces, spaces,

View File

@ -1,19 +1,74 @@
use crate::CoreApi; use crate::CoreApi;
use glib::Object; use glib::{subclass, Object, ParamSpec, Properties, Value};
use gtk::{prelude::*, subclass::prelude::*}; use gtk::{glib, prelude::*, subclass::prelude::*};
use kifu_core::ui::{NewGameView, Player}; use kifu_core::ui::{NewGameView, PlayerElement};
use std::{cell::RefCell, rc::Rc}; 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<PlayerDataEntryPrivate>) @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(&gtk::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 { pub struct NewGamePrivate {
black_player: gtk::Label, black_player: Rc<RefCell<Option<PlayerDataEntry>>>,
white_player: gtk::Label, white_player: Rc<RefCell<Option<PlayerDataEntry>>>,
} }
impl Default for NewGamePrivate { impl Default for NewGamePrivate {
fn default() -> Self { fn default() -> Self {
Self { Self {
black_player: gtk::Label::new(None), black_player: Rc::new(RefCell::new(None)),
white_player: gtk::Label::new(None), white_player: Rc::new(RefCell::new(None)),
} }
} }
} }
@ -34,30 +89,26 @@ glib::wrapper! {
} }
impl NewGame { impl NewGame {
pub fn new(_api: CoreApi, view: NewGameView) -> NewGame { pub fn new(api: CoreApi, view: NewGameView) -> NewGame {
let s: Self = Object::builder().build(); let s: Self = Object::builder().build();
match view.black_player { let black_player = PlayerDataEntry::new(view.black_player);
Player::Hotseat(player) => { s.attach(&black_player, 1, 1, 1, 1);
if let Some(name) = player.name { *s.imp().black_player.borrow_mut() = Some(black_player);
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 { let white_player = PlayerDataEntry::new(view.white_player);
Player::Hotseat(player) => { s.attach(&white_player, 2, 1, 1, 1);
if let Some(name) = player.name { *s.imp().white_player.borrow_mut() = Some(white_player);
s.imp().white_player.set_text(name.as_ref());
} let new_game_button = gtk::Button::builder().label("Start Game").build();
} s.attach(&new_game_button, 2, 2, 1, 1);
Player::Remote(_) => s.imp().black_player.set_text("remote player"),
Player::Bot(_) => s.imp().black_player.set_text("bot player"), /*
} new_game_button.connect_clicked(move |_| {
s.attach(&s.imp().white_player, 2, 1, 1, 1); api.dispatch(CoreRequest::CreatGameRequest(CreateGameRequest {
}));
});
*/
s s
} }