Update the build environment and some architectural elements of the Kifu app #210

Merged
savanni merged 13 commits from kifu/flake into main 2024-02-28 04:42:58 +00:00
6 changed files with 117 additions and 29 deletions
Showing only changes of commit f2a63cf3c3 - Show all commits

View File

@ -2,12 +2,12 @@ use crate::{BoardError, Color, Size};
use std::collections::HashSet;
#[derive(Clone, Debug, Default)]
pub struct Board {
pub struct Goban {
pub size: Size,
pub groups: Vec<Group>,
}
impl std::fmt::Display for Board {
impl std::fmt::Display for Goban {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, " ")?;
// for c in 'A'..'U' {
@ -31,7 +31,7 @@ impl std::fmt::Display for Board {
}
}
impl PartialEq for Board {
impl PartialEq for Goban {
fn eq(&self, other: &Self) -> bool {
if self.size != other.size {
return false;
@ -51,7 +51,7 @@ impl PartialEq for Board {
}
}
impl Board {
impl Goban {
pub fn new() -> Self {
Self {
size: Size {
@ -77,7 +77,7 @@ pub struct Coordinate {
pub row: u8,
}
impl Board {
impl Goban {
pub fn place_stone(mut self, coordinate: Coordinate, color: Color) -> Result<Self, BoardError> {
if self.stone(&coordinate).is_some() {
return Err(BoardError::InvalidPosition);
@ -224,8 +224,8 @@ mod test {
* A stone placed in a suicidal position is legal if it captures other stones first.
*/
fn with_example_board(test: impl FnOnce(Board)) {
let board = Board::from_coordinates(
fn with_example_board(test: impl FnOnce(Goban)) {
let board = Goban::from_coordinates(
vec![
(Coordinate { column: 3, row: 3 }, Color::White),
(Coordinate { column: 3, row: 4 }, Color::White),
@ -284,7 +284,7 @@ mod test {
#[test]
fn it_gets_adjacencies_for_coordinate() {
let board = Board::new();
let board = Goban::new();
for column in 0..19 {
for row in 0..19 {
for coordinate in board.adjacencies(&Coordinate { column, row }) {
@ -302,7 +302,7 @@ mod test {
#[test]
fn it_counts_individual_liberties() {
let board = Board::from_coordinates(
let board = Goban::from_coordinates(
vec![
(Coordinate { column: 3, row: 3 }, Color::White),
(Coordinate { column: 0, row: 3 }, Color::White),
@ -357,7 +357,7 @@ mod test {
#[test]
fn stones_share_liberties() {
with_example_board(|board: Board| {
with_example_board(|board: Goban| {
let test_cases = vec![
(
board.clone(),
@ -567,11 +567,11 @@ mod test {
#[test]
fn validate_group_comparisons() {
{
let b1 = Board::from_coordinates(
let b1 = Goban::from_coordinates(
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
)
.unwrap();
let b2 = Board::from_coordinates(
let b2 = Goban::from_coordinates(
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
)
.unwrap();
@ -580,7 +580,7 @@ mod test {
}
{
let b1 = Board::from_coordinates(
let b1 = Goban::from_coordinates(
vec![
(Coordinate { column: 7, row: 9 }, Color::White),
(Coordinate { column: 8, row: 10 }, Color::White),
@ -588,7 +588,7 @@ mod test {
.into_iter(),
)
.unwrap();
let b2 = Board::from_coordinates(
let b2 = Goban::from_coordinates(
vec![
(Coordinate { column: 8, row: 10 }, Color::White),
(Coordinate { column: 7, row: 9 }, Color::White),
@ -603,7 +603,7 @@ mod test {
#[test]
fn two_boards_can_be_compared() {
let board = Board::from_coordinates(
let board = Goban::from_coordinates(
vec![
(Coordinate { column: 7, row: 9 }, Color::White),
(Coordinate { column: 8, row: 8 }, Color::White),

View File

@ -2,8 +2,8 @@ extern crate config_derive;
mod api;
pub use api::{
ChangeSettingRequest, Core, CoreRequest, CoreResponse, CreateGameRequest,
HotseatPlayerRequest, PlayerInfoRequest, CoreNotification,
ChangeSettingRequest, Core, CoreNotification, CoreRequest, CoreResponse, CreateGameRequest,
HotseatPlayerRequest, PlayerInfoRequest,
};
mod board;
@ -12,6 +12,6 @@ pub use board::*;
mod database;
mod types;
pub use types::{BoardError, Color, Rank, Size, DatabasePath, Config, ConfigOption};
pub use types::{BoardError, Color, Config, ConfigOption, DatabasePath, Player, Rank, Size};
pub mod ui;

View File

@ -1,6 +1,6 @@
use crate::{
api::PlayStoneRequest,
board::{Board, Coordinate},
board::{Goban, Coordinate},
database::Database,
};
use config::define_config;
@ -132,8 +132,8 @@ pub struct Player {
#[derive(Debug)]
pub struct GameState {
pub board: Board,
pub past_positions: Vec<Board>,
pub board: Goban,
pub past_positions: Vec<Goban>,
pub conversation: Vec<String>,
pub current_player: Color,
@ -148,7 +148,7 @@ pub struct GameState {
impl Default for GameState {
fn default() -> Self {
Self {
board: Board::new(),
board: Goban::new(),
past_positions: vec![],
conversation: vec![],
current_player: Color::Black,
@ -200,7 +200,7 @@ mod test {
#[test]
fn current_player_remains_the_same_after_self_capture() {
let mut state = GameState::default();
state.board = Board::from_coordinates(
state.board = Goban::from_coordinates(
vec![
(Coordinate { column: 17, row: 0 }, Color::White),
(Coordinate { column: 17, row: 1 }, Color::White),
@ -221,7 +221,7 @@ mod test {
#[test]
fn ko_rules_are_enforced() {
let mut state = GameState::default();
state.board = Board::from_coordinates(
state.board = Goban::from_coordinates(
vec![
(Coordinate { column: 7, row: 9 }, Color::White),
(Coordinate { column: 8, row: 8 }, Color::White),

View File

@ -1,7 +1,7 @@
use crate::types::{Color, Size};
use crate::{
api::{CoreRequest, PlayStoneRequest},
Board, Coordinate,
Goban, Coordinate,
};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
@ -80,8 +80,8 @@ impl BoardElement {
}
}
impl From<&Board> for BoardElement {
fn from(board: &Board) -> Self {
impl From<&Goban> for BoardElement {
fn from(board: &Goban) -> Self {
let spaces: Vec<IntersectionElement> =
(0..board.size.height)
.map(|row| {

View File

@ -0,0 +1,86 @@
/*
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of Kifu.
Kifu is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Kifu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along with Kifu. If not, see <https://www.gnu.org/licenses/>.
*/
use async_channel::Receiver;
use async_std::task::{spawn, yield_now, JoinHandle};
use kifu_core::{Color, Core, CoreNotification, Goban, Player};
use std::{
sync::{Arc, RwLock},
time::Duration,
};
struct GameViewModelPrivate {
white: Player, /* Maybe this should be PlayerState, instead, combining the player info, current clock, and current captures. */
black: Player,
current: Color,
goban: Goban, /* Or perhaps clocks, captures, and the board should be bound into GameState. */
white_clock: Duration,
black_clock: Duration,
}
/// The Game View Model manages the current state of the game. It shows the two player cards, the board, the current capture count, the current player, and it maintains the UI for the clock (bearing in mind that the real clock is managed in the core). This view model should only be created once the details of the game, whether a game in progress or a new game (this view model won't know the difference) is known.
pub struct GameViewModel {
core: Core,
notification_handler: JoinHandle<()>,
widget: gtk::Box,
data: Arc<RwLock<GameViewModelPrivate>>,
}
impl GameViewModelPrivate {
fn handle(&mut self, message: CoreNotification) {}
}
impl GameViewModel {
pub fn new(
white: Player,
black: Player,
current: Color,
goban: Goban,
white_clock: Duration,
black_clock: Duration,
core: Core,
notifications: Receiver<CoreNotification>,
) -> Self {
let data = Arc::new(RwLock::new(GameViewModelPrivate {
white,
black,
current,
goban,
white_clock,
black_clock,
}));
let handler = spawn({
let data = data.clone();
async move {
loop {
match notifications.recv().await {
Ok(msg) => data.write().unwrap().handle(msg),
Err(err) => unimplemented!("Should display an error message in the UI"),
}
yield_now().await;
}
}
});
Self {
core,
notification_handler: handler,
widget: gtk::Box::new(gtk::Orientation::Horizontal, 0),
data,
}
}
}

View File

@ -14,7 +14,6 @@ General Public License for more details.
You should have received a copy of the GNU General Public License along with Kifu. If not, see <https://www.gnu.org/licenses/>.
*/
/*
Every view model requires a reference to the app so that it can call functions on the core, and a notification receiver so that it can receive messages from the core.
@ -23,3 +22,6 @@ The view model is primary over the view. It will construct the view, it can make
mod database_view_model;
pub use database_view_model::DatabaseViewModel;
mod game_view_model;
pub use game_view_model::GameViewModel;