Start on the NewGame page

This commit is contained in:
Savanni D'Gerinel 2023-05-26 00:16:40 -04:00
parent 6f6579b7a7
commit ef045117be
8 changed files with 229 additions and 23 deletions

View File

@ -1,5 +1,5 @@
use crate::types::AppState; use crate::types::AppState;
use crate::ui::{playing_field, PlayingFieldView}; use crate::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;
@ -8,8 +8,12 @@ use typeshare::typeshare;
#[typeshare] #[typeshare]
#[serde(tag = "type", content = "content")] #[serde(tag = "type", content = "content")]
pub enum CoreRequest { pub enum CoreRequest {
// CreateGameRequest(CreateGameRequest),
LaunchScreen,
NewGame,
PlayingField, PlayingField,
PlayStoneRequest(PlayStoneRequest), PlayStoneRequest(PlayStoneRequest),
StartGame,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
@ -23,6 +27,7 @@ pub struct PlayStoneRequest {
#[typeshare] #[typeshare]
#[serde(tag = "type", content = "content")] #[serde(tag = "type", content = "content")]
pub enum CoreResponse { pub enum CoreResponse {
NewGameView(NewGameView),
PlayingFieldView(PlayingFieldView), PlayingFieldView(PlayingFieldView),
} }
@ -40,6 +45,23 @@ impl CoreApp {
pub async fn dispatch(&self, request: CoreRequest) -> CoreResponse { pub async fn dispatch(&self, request: CoreRequest) -> CoreResponse {
match request { 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 => { CoreRequest::PlayingField => {
let app_state = self.state.read().unwrap(); let app_state = self.state.read().unwrap();
let game = app_state.game.as_ref().unwrap(); let game = app_state.game.as_ref().unwrap();
@ -52,6 +74,9 @@ 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::StartGame => {
unimplemented!()
}
} }
} }

View File

@ -65,8 +65,9 @@ impl AppState {
} }
} }
#[derive(Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
enum Rank { #[typeshare]
pub enum Rank {
Kyu(u8), Kyu(u8),
Dan(u8), Dan(u8),
Pro(u8), Pro(u8),

View File

@ -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 {
}

View File

@ -1,6 +1,12 @@
mod playing_field; mod playing_field;
pub use playing_field::{playing_field, PlayingFieldView}; 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; mod types;
pub use types::{ pub use types::{
BoardElement, ChatElement, IntersectionElement, PlayerCardElement, StoneElement, BoardElement, ChatElement, IntersectionElement, PlayerCardElement, StoneElement,

View File

@ -0,0 +1,70 @@
use crate::types::Rank;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct Menu<T: PartialEq + Eq> {
current: Option<T>,
options: Vec<T>,
}
impl<T: PartialEq + Eq> Menu<T> {
pub fn new(options: Vec<T>, current: Option<T>) -> Menu<T> {
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<String>,
pub rank: Option<Rank>,
}
#[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,
}
}

View File

@ -1,8 +1,46 @@
use gtk::prelude::*; use gtk::prelude::*;
use kifu_core::{CoreApp, CoreRequest, CoreResponse}; use kifu_core::{CoreApp, CoreRequest, CoreResponse};
use kifu_gtk::{ui::PlayingField, CoreApi}; use kifu_gtk::{
ui::{NewGame, PlayingField},
CoreApi,
};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
fn perftrace<F, A>(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() { fn main() {
gio::resources_register_include!("com.luminescent-dreams.kifu-gtk.gresource") gio::resources_register_include!("com.luminescent-dreams.kifu-gtk.gresource")
.expect("Failed to register resources"); .expect("Failed to register resources");
@ -44,30 +82,15 @@ fn main() {
gtk_rx.attach(None, { gtk_rx.attach(None, {
let api = api.clone(); let api = api.clone();
let playing_field = Arc::new(RwLock::new(None));
move |message| { move |message| {
match message { perftrace("handle_response", || {
CoreResponse::PlayingFieldView(view) => { handle_response(api.clone(), window.clone(), message)
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);
}
}
Continue(true) Continue(true)
} }
}); });
api.dispatch(CoreRequest::PlayingField); api.dispatch(CoreRequest::NewGame);
} }
}); });

View File

@ -7,6 +7,9 @@ pub use chat::Chat;
mod playing_field; mod playing_field;
pub use playing_field::PlayingField; pub use playing_field::PlayingField;
mod new_game;
pub use new_game::NewGame;
mod board; mod board;
pub use board::Board; pub use board::Board;

View File

@ -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<NewGamePrivate>) @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
}
}