Compare commits

..

No commits in common. "59a8f252da7fdafe07b5c5cb9a3d90b6b3abeaee" and "481226e8c6b5c67db0878619fd6d3904341382b5" have entirely different histories.

9 changed files with 79 additions and 235 deletions

View File

@ -76,7 +76,7 @@
inherit pkgs; inherit pkgs;
buildRustCrateForPkgs = customBuildInfo; buildRustCrateForPkgs = customBuildInfo;
rootFeatures = [ "screenplay" ]; rootFeatures = [ "screenplay" ];
release = true; release = false;
}).rootCrate.build; }).rootCrate.build;
}; };
}; };

View File

@ -2,19 +2,11 @@ use crate::types::AppState;
use crate::ui::{playing_field, PlayingFieldView}; use crate::ui::{playing_field, PlayingFieldView};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Request { pub enum Request {
PlayingField, PlayingField,
PlayStoneRequest(PlayStoneRequest),
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Debug)]
pub struct PlayStoneRequest {
pub column: u8,
pub row: u8,
}
#[derive(Clone, Debug)]
pub enum Response { pub enum Response {
PlayingFieldView(PlayingFieldView), PlayingFieldView(PlayingFieldView),
} }
@ -33,18 +25,7 @@ impl CoreApp {
pub async fn dispatch(&self, request: Request) -> Response { pub async fn dispatch(&self, request: Request) -> Response {
match request { match request {
Request::PlayingField => { Request::PlayingField => Response::PlayingFieldView(playing_field()),
let app_state = self.state.read().unwrap();
let game = app_state.game.as_ref().unwrap();
Response::PlayingFieldView(playing_field(game))
}
Request::PlayStoneRequest(request) => {
let mut app_state = self.state.write().unwrap();
app_state.place_stone(request);
let game = app_state.game.as_ref().unwrap();
Response::PlayingFieldView(playing_field(game))
}
} }
} }

View File

@ -1,4 +1,3 @@
use crate::api::PlayStoneRequest;
use std::time::Duration; use std::time::Duration;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -22,24 +21,13 @@ impl Default for Size {
} }
} }
pub struct AppState { pub(crate) struct AppState {
pub game: Option<GameState>, game: Option<GameState>,
} }
impl AppState { impl AppState {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self { game: None }
game: Some(GameState::new()),
}
}
pub fn place_stone(&mut self, req: PlayStoneRequest) {
match self.game {
Some(ref mut game) => {
game.place_stone(req.column, req.row);
}
None => {}
}
} }
} }
@ -67,82 +55,18 @@ impl From<Rank> for String {
} }
} }
pub struct Player { struct Player {
name: String, name: String,
rank: Rank, rank: Rank,
} }
pub struct GameState { pub(crate) struct GameState {
pub board: Board, goban: Vec<Vec<Color>>,
pub conversation: Vec<String>, conversation: Vec<String>,
pub current_player: Color,
pub white_player: Player, white_player: Player,
pub black_player: Player, black_player: Player,
pub white_clock: Duration, white_clock: Duration,
pub black_clock: Duration, black_clock: Duration,
}
impl GameState {
fn new() -> GameState {
GameState {
board: Board::new(),
conversation: vec![],
current_player: Color::Black,
white_player: Player {
name: "Savanni".to_owned(),
rank: Rank::Kyu(10),
},
black_player: Player {
name: "Opal".to_owned(),
rank: Rank::Kyu(10),
},
white_clock: Duration::from_secs(600),
black_clock: Duration::from_secs(600),
}
}
fn place_stone(&mut self, column: u8, row: u8) {
self.board.place_stone(column, row, self.current_player);
match self.current_player {
Color::White => self.current_player = Color::Black,
Color::Black => self.current_player = Color::White,
}
}
}
pub struct Board {
pub size: Size,
pub spaces: Vec<Option<Color>>,
}
impl Board {
fn new() -> Self {
let mut spaces = Vec::new();
for _ in 0..19 * 19 {
spaces.push(None);
}
Self {
size: Size {
width: 19,
height: 19,
},
spaces,
}
}
pub fn place_stone(&mut self, column: u8, row: u8, stone: Color) {
let addr = self.addr(column, row);
self.spaces[addr] = Some(stone);
}
pub fn stone(&self, column: u8, row: u8) -> Option<Color> {
let addr = self.addr(column, row);
self.spaces[addr]
}
fn addr(&self, column: u8, row: u8) -> usize {
((row as usize) * (self.size.width as usize) + (column as usize)) as usize
}
} }

View File

@ -1,5 +1,5 @@
use crate::types::{Color, Size}; use crate::types::{Color, Size};
use crate::{types::GameState, ui::types}; use crate::ui::types;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PlayingFieldView { pub struct PlayingFieldView {
@ -11,8 +11,8 @@ pub struct PlayingFieldView {
pub current_player: Color, pub current_player: Color,
} }
pub fn playing_field(game: &GameState) -> PlayingFieldView { pub fn playing_field() -> PlayingFieldView {
let board = types::BoardElement::from(&game.board); let board = types::BoardElement::default();
let player_card_black = types::PlayerCardElement { let player_card_black = types::PlayerCardElement {
color: Color::Black, color: Color::Black,
@ -42,6 +42,6 @@ pub fn playing_field(game: &GameState) -> PlayingFieldView {
player_card_white, player_card_white,
chat, chat,
message, message,
current_player: game.current_player, current_player: Color::White,
} }
} }

View File

@ -1,8 +1,4 @@
use crate::types::{Color, Size}; use crate::types::{Color, Size};
use crate::{
api::{PlayStoneRequest, Request},
types::Board,
};
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct Jitter { pub struct Jitter {
@ -25,10 +21,16 @@ impl StoneElement {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PlayStoneRequest {
col: u8,
row: u8,
}
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum IntersectionElement { pub enum IntersectionElement {
Unplayable, Unplayable,
Empty(Request), Empty(PlayStoneRequest),
Filled(StoneElement), Filled(StoneElement),
} }
@ -43,12 +45,7 @@ impl BoardElement {
let spaces: Vec<IntersectionElement> = (0..size.height) let spaces: Vec<IntersectionElement> = (0..size.height)
.map(|row| { .map(|row| {
(0..size.width) (0..size.width)
.map(|column| { .map(|col| IntersectionElement::Empty(PlayStoneRequest { col, row }))
IntersectionElement::Empty(Request::PlayStoneRequest(PlayStoneRequest {
column,
row,
}))
})
.collect::<Vec<IntersectionElement>>() .collect::<Vec<IntersectionElement>>()
}) })
.collect::<Vec<Vec<IntersectionElement>>>() .collect::<Vec<Vec<IntersectionElement>>>()
@ -56,38 +53,17 @@ impl BoardElement {
Self { size, spaces } Self { size, spaces }
} }
pub fn stone(&self, column: u8, row: u8) -> IntersectionElement { pub fn stone_mut<'a>(&'a mut self, row: u8, col: u8) -> &'a mut IntersectionElement {
let addr = self.addr(column, row); let addr = self.addr(row, col);
self.spaces[addr] &mut self.spaces[addr]
} }
fn addr(&self, column: u8, row: u8) -> usize { pub fn stone(&self, row: u8, col: u8) -> IntersectionElement {
((row as usize) * (self.size.width as usize) + (column as usize)) as usize self.spaces[self.addr(row, col)].clone()
}
} }
impl From<&Board> for BoardElement { fn addr(&self, row: u8, col: u8) -> usize {
fn from(board: &Board) -> Self { ((row as usize) * (self.size.width as usize) + (col as usize)) as usize
let spaces: Vec<IntersectionElement> = (0..board.size.height)
.map(|row| {
(0..board.size.width)
.map(|column| match board.stone(column, row) {
Some(color) => IntersectionElement::Filled(StoneElement {
jitter: Jitter { x: 0, y: 0 },
color,
}),
None => IntersectionElement::Empty(Request::PlayStoneRequest(
PlayStoneRequest { column, row },
)),
})
.collect::<Vec<IntersectionElement>>()
})
.collect::<Vec<Vec<IntersectionElement>>>()
.concat();
Self {
size: board.size,
spaces,
}
} }
} }

View File

@ -1,21 +1 @@
pub mod ui; pub mod ui;
use kifu_core::{CoreApp, Request, Response};
use std::sync::Arc;
use tokio::runtime::Runtime;
#[derive(Clone)]
pub struct CoreApi {
pub gtk_tx: gtk::glib::Sender<Response>,
pub rt: Arc<Runtime>,
pub core: CoreApp,
}
impl CoreApi {
pub fn dispatch(&self, request: Request) {
self.rt.spawn({
let gtk_tx = self.gtk_tx.clone();
let core = self.core.clone();
async move { gtk_tx.send(core.dispatch(request).await) }
});
}
}

View File

@ -1,9 +1,9 @@
use gio::resources_lookup_data; use gio::resources_lookup_data;
use gtk::prelude::*; use gtk::prelude::*;
use kifu_core::{CoreApp, Request, Response}; use kifu_core::{CoreApp, Request, Response};
use kifu_gtk::{ui::PlayingField, CoreApi}; use kifu_gtk::ui::PlayingField;
use std::{ use std::{
sync::{Arc, Mutex, RwLock}, sync::{Arc, Mutex},
time::Duration, time::Duration,
}; };
use tokio::{ use tokio::{
@ -11,6 +11,23 @@ use tokio::{
sync::mpsc::{Receiver, Sender}, sync::mpsc::{Receiver, Sender},
}; };
#[derive(Clone)]
pub struct CoreApi {
gtk_tx: gtk::glib::Sender<Response>,
rt: Arc<Runtime>,
core: CoreApp,
}
impl CoreApi {
pub fn dispatch(&self, request: Request) {
self.rt.spawn({
let gtk_tx = self.gtk_tx.clone();
let core = self.core.clone();
async move { gtk_tx.send(core.dispatch(request).await) }
});
}
}
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");
@ -50,29 +67,15 @@ fn main() {
let window = gtk::ApplicationWindow::new(app); let window = gtk::ApplicationWindow::new(app);
window.present(); window.present();
gtk_rx.attach(None, { gtk_rx.attach(None, move |message| {
let api = api.clone(); println!("message: {:?}", message);
let playing_field = Arc::new(RwLock::new(None));
move |message| {
match message { match message {
Response::PlayingFieldView(view) => { Response::PlayingFieldView(view) => {
let api = api.clone(); let playing_field = PlayingField::new(view);
window.set_child(Some(&playing_field));
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(Request::PlayingField); api.dispatch(Request::PlayingField);

View File

@ -1,4 +1,3 @@
use crate::CoreApi;
use gio::resources_lookup_data; use gio::resources_lookup_data;
use glib::Object; use glib::Object;
use gtk::{ use gtk::{
@ -28,8 +27,6 @@ pub struct BoardPrivate {
current_player: Rc<RefCell<Color>>, current_player: Rc<RefCell<Color>>,
board: Rc<RefCell<BoardElement>>, board: Rc<RefCell<BoardElement>>,
cursor_location: Rc<RefCell<Addr>>, cursor_location: Rc<RefCell<Addr>>,
api: Rc<RefCell<Option<CoreApi>>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -44,7 +41,6 @@ impl ObjectSubclass for BoardPrivate {
current_player: Rc::new(RefCell::new(Color::Black)), current_player: Rc::new(RefCell::new(Color::Black)),
board: Default::default(), board: Default::default(),
cursor_location: Default::default(), cursor_location: Default::default(),
api: Default::default(),
} }
} }
} }
@ -82,7 +78,6 @@ impl ObjectImpl for BoardPrivate {
self.drawing_area self.drawing_area
.set_draw_func(move |_, context, width, height| { .set_draw_func(move |_, context, width, height| {
let render_start = std::time::Instant::now();
let board = board.borrow(); let board = board.borrow();
match background { match background {
Ok(Some(ref background)) => { Ok(Some(ref background)) => {
@ -126,10 +121,11 @@ impl ObjectImpl for BoardPrivate {
(0..19).for_each(|col| { (0..19).for_each(|col| {
(0..19).for_each(|row| { (0..19).for_each(|row| {
match board.stone(row, col) { match board.stone(row, col) {
IntersectionElement::Unplayable => {}
IntersectionElement::Empty(request) => {}
IntersectionElement::Filled(stone) => { IntersectionElement::Filled(stone) => {
pen.stone(&context, row, col, stone.color); pen.stone(&context, row, col, stone.color);
} }
_ => {}
}; };
}) })
}); });
@ -144,8 +140,6 @@ impl ObjectImpl for BoardPrivate {
), ),
_ => {} _ => {}
} }
let render_end = std::time::Instant::now();
println!("board rendering time: {:?}", render_end - render_start);
}); });
let motion_controller = gtk::EventControllerMotion::new(); let motion_controller = gtk::EventControllerMotion::new();
@ -175,17 +169,12 @@ impl ObjectImpl for BoardPrivate {
{ {
let board = self.board.clone(); let board = self.board.clone();
let cursor = self.cursor_location.clone(); let cursor = self.cursor_location.clone();
let api = self.api.clone();
gesture.connect_released(move |_, _, _, _| { gesture.connect_released(move |_, _, _, _| {
let board = board.borrow(); let board = board.borrow();
let cursor = cursor.borrow(); let cursor = cursor.borrow();
match board.stone(cursor.row, cursor.column) { match board.stone(cursor.row, cursor.column) {
IntersectionElement::Empty(request) => { IntersectionElement::Empty(request) => {
println!("need to send request: {:?}", request); println!("need to send request: {:?}", request)
api.borrow()
.as_ref()
.expect("API must exist")
.dispatch(request);
} }
_ => {} _ => {}
} }
@ -204,12 +193,9 @@ glib::wrapper! {
} }
impl Board { impl Board {
pub fn new(api: CoreApi) -> Self { pub fn new() -> Self {
let s: Self = Object::builder().build(); let s: Self = Object::builder().build();
*s.imp().api.borrow_mut() = Some(api);
s.attach(&s.imp().drawing_area, 1, 1, 1, 1); s.attach(&s.imp().drawing_area, 1, 1, 1, 1);
s s
} }

View File

@ -1,5 +1,4 @@
use crate::ui::{Board, Chat, PlayerCard}; use crate::ui::{Board, Chat, PlayerCard};
use crate::CoreApi;
use glib::Object; use glib::Object;
use gtk::{prelude::*, subclass::prelude::*}; use gtk::{prelude::*, subclass::prelude::*};
use kifu_core::{ use kifu_core::{
@ -24,7 +23,7 @@ pub struct PlayingFieldView {
*/ */
pub struct PlayingFieldPrivate { pub struct PlayingFieldPrivate {
board: Rc<RefCell<Option<Board>>>, goban: Board,
player_card_white: Rc<RefCell<Option<PlayerCard>>>, player_card_white: Rc<RefCell<Option<PlayerCard>>>,
player_card_black: Rc<RefCell<Option<PlayerCard>>>, player_card_black: Rc<RefCell<Option<PlayerCard>>>,
chat: Rc<RefCell<Option<Chat>>>, chat: Rc<RefCell<Option<Chat>>>,
@ -33,7 +32,7 @@ pub struct PlayingFieldPrivate {
impl Default for PlayingFieldPrivate { impl Default for PlayingFieldPrivate {
fn default() -> Self { fn default() -> Self {
Self { Self {
board: Default::default(), goban: Board::new(),
player_card_white: Rc::new(RefCell::new(None)), player_card_white: Rc::new(RefCell::new(None)),
player_card_black: Rc::new(RefCell::new(None)), player_card_black: Rc::new(RefCell::new(None)),
chat: Rc::new(RefCell::new(None)), chat: Rc::new(RefCell::new(None)),
@ -46,6 +45,15 @@ impl ObjectSubclass for PlayingFieldPrivate {
const NAME: &'static str = "PlayingField"; const NAME: &'static str = "PlayingField";
type Type = PlayingField; type Type = PlayingField;
type ParentType = gtk::Grid; type ParentType = gtk::Grid;
fn new() -> Self {
Self {
goban: Board::new(),
player_card_white: Rc::new(RefCell::new(None)),
player_card_black: Rc::new(RefCell::new(None)),
chat: Rc::new(RefCell::new(None)),
}
}
} }
impl ObjectImpl for PlayingFieldPrivate {} impl ObjectImpl for PlayingFieldPrivate {}
@ -57,19 +65,16 @@ glib::wrapper! {
} }
impl PlayingField { impl PlayingField {
pub fn new(api: CoreApi, view: PlayingFieldView) -> PlayingField { pub fn new(view: PlayingFieldView) -> PlayingField {
let s: Self = Object::builder().build(); let s: Self = Object::builder().build();
let player_card_white = PlayerCard::new(view.player_card_white); let player_card_white = PlayerCard::new(view.player_card_white);
let player_card_black = PlayerCard::new(view.player_card_black); let player_card_black = PlayerCard::new(view.player_card_black);
let chat = Chat::new(view.chat); let chat = Chat::new(view.chat);
*s.imp().board.borrow_mut() = Some(Board::new(api)); s.imp().goban.set_board(view.board);
s.imp()
.board s.attach(&s.imp().goban, 1, 1, 1, 2);
.borrow()
.as_ref()
.map(|board| s.attach(board, 1, 1, 1, 2));
s.attach(&player_card_black, 2, 1, 1, 1); s.attach(&player_card_black, 2, 1, 1, 1);
s.attach(&player_card_white, 3, 1, 1, 1); s.attach(&player_card_white, 3, 1, 1, 1);
s.attach(&chat, 2, 2, 2, 1); s.attach(&chat, 2, 2, 2, 1);
@ -77,21 +82,10 @@ impl PlayingField {
*s.imp().player_card_white.borrow_mut() = Some(player_card_white); *s.imp().player_card_white.borrow_mut() = Some(player_card_white);
*s.imp().player_card_black.borrow_mut() = Some(player_card_black); *s.imp().player_card_black.borrow_mut() = Some(player_card_black);
*s.imp().chat.borrow_mut() = Some(chat); *s.imp().chat.borrow_mut() = Some(chat);
s.imp().goban.set_current_player(view.current_player);
s.imp().board.borrow().as_ref().map(|board| {
board.set_board(view.board);
board.set_current_player(view.current_player);
});
s s
} }
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);
});
}
} }
#[cfg(feature = "screenplay")] #[cfg(feature = "screenplay")]