Update the build environment and some architectural elements of the Kifu app #210
|
@ -2,12 +2,12 @@ use crate::{BoardError, Color, Size};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Board {
|
pub struct Goban {
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
pub groups: Vec<Group>,
|
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> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
// for c in 'A'..'U' {
|
// 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 {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
if self.size != other.size {
|
if self.size != other.size {
|
||||||
return false;
|
return false;
|
||||||
|
@ -51,7 +51,7 @@ impl PartialEq for Board {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl Goban {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
size: Size {
|
size: Size {
|
||||||
|
@ -77,7 +77,7 @@ pub struct Coordinate {
|
||||||
pub row: u8,
|
pub row: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl Goban {
|
||||||
pub fn place_stone(mut self, coordinate: Coordinate, color: Color) -> Result<Self, BoardError> {
|
pub fn place_stone(mut self, coordinate: Coordinate, color: Color) -> Result<Self, BoardError> {
|
||||||
if self.stone(&coordinate).is_some() {
|
if self.stone(&coordinate).is_some() {
|
||||||
return Err(BoardError::InvalidPosition);
|
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.
|
* A stone placed in a suicidal position is legal if it captures other stones first.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fn with_example_board(test: impl FnOnce(Board)) {
|
fn with_example_board(test: impl FnOnce(Goban)) {
|
||||||
let board = Board::from_coordinates(
|
let board = Goban::from_coordinates(
|
||||||
vec![
|
vec![
|
||||||
(Coordinate { column: 3, row: 3 }, Color::White),
|
(Coordinate { column: 3, row: 3 }, Color::White),
|
||||||
(Coordinate { column: 3, row: 4 }, Color::White),
|
(Coordinate { column: 3, row: 4 }, Color::White),
|
||||||
|
@ -284,7 +284,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_gets_adjacencies_for_coordinate() {
|
fn it_gets_adjacencies_for_coordinate() {
|
||||||
let board = Board::new();
|
let board = Goban::new();
|
||||||
for column in 0..19 {
|
for column in 0..19 {
|
||||||
for row in 0..19 {
|
for row in 0..19 {
|
||||||
for coordinate in board.adjacencies(&Coordinate { column, row }) {
|
for coordinate in board.adjacencies(&Coordinate { column, row }) {
|
||||||
|
@ -302,7 +302,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_counts_individual_liberties() {
|
fn it_counts_individual_liberties() {
|
||||||
let board = Board::from_coordinates(
|
let board = Goban::from_coordinates(
|
||||||
vec![
|
vec![
|
||||||
(Coordinate { column: 3, row: 3 }, Color::White),
|
(Coordinate { column: 3, row: 3 }, Color::White),
|
||||||
(Coordinate { column: 0, row: 3 }, Color::White),
|
(Coordinate { column: 0, row: 3 }, Color::White),
|
||||||
|
@ -357,7 +357,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stones_share_liberties() {
|
fn stones_share_liberties() {
|
||||||
with_example_board(|board: Board| {
|
with_example_board(|board: Goban| {
|
||||||
let test_cases = vec![
|
let test_cases = vec![
|
||||||
(
|
(
|
||||||
board.clone(),
|
board.clone(),
|
||||||
|
@ -567,11 +567,11 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_group_comparisons() {
|
fn validate_group_comparisons() {
|
||||||
{
|
{
|
||||||
let b1 = Board::from_coordinates(
|
let b1 = Goban::from_coordinates(
|
||||||
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
|
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let b2 = Board::from_coordinates(
|
let b2 = Goban::from_coordinates(
|
||||||
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
|
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -580,7 +580,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let b1 = Board::from_coordinates(
|
let b1 = Goban::from_coordinates(
|
||||||
vec![
|
vec![
|
||||||
(Coordinate { column: 7, row: 9 }, Color::White),
|
(Coordinate { column: 7, row: 9 }, Color::White),
|
||||||
(Coordinate { column: 8, row: 10 }, Color::White),
|
(Coordinate { column: 8, row: 10 }, Color::White),
|
||||||
|
@ -588,7 +588,7 @@ mod test {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let b2 = Board::from_coordinates(
|
let b2 = Goban::from_coordinates(
|
||||||
vec![
|
vec![
|
||||||
(Coordinate { column: 8, row: 10 }, Color::White),
|
(Coordinate { column: 8, row: 10 }, Color::White),
|
||||||
(Coordinate { column: 7, row: 9 }, Color::White),
|
(Coordinate { column: 7, row: 9 }, Color::White),
|
||||||
|
@ -603,7 +603,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_boards_can_be_compared() {
|
fn two_boards_can_be_compared() {
|
||||||
let board = Board::from_coordinates(
|
let board = Goban::from_coordinates(
|
||||||
vec![
|
vec![
|
||||||
(Coordinate { column: 7, row: 9 }, Color::White),
|
(Coordinate { column: 7, row: 9 }, Color::White),
|
||||||
(Coordinate { column: 8, row: 8 }, Color::White),
|
(Coordinate { column: 8, row: 8 }, Color::White),
|
||||||
|
|
|
@ -2,8 +2,8 @@ extern crate config_derive;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
pub use api::{
|
pub use api::{
|
||||||
ChangeSettingRequest, Core, CoreRequest, CoreResponse, CreateGameRequest,
|
ChangeSettingRequest, Core, CoreNotification, CoreRequest, CoreResponse, CreateGameRequest,
|
||||||
HotseatPlayerRequest, PlayerInfoRequest, CoreNotification,
|
HotseatPlayerRequest, PlayerInfoRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod board;
|
mod board;
|
||||||
|
@ -12,6 +12,6 @@ pub use board::*;
|
||||||
mod database;
|
mod database;
|
||||||
|
|
||||||
mod types;
|
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;
|
pub mod ui;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
api::PlayStoneRequest,
|
api::PlayStoneRequest,
|
||||||
board::{Board, Coordinate},
|
board::{Goban, Coordinate},
|
||||||
database::Database,
|
database::Database,
|
||||||
};
|
};
|
||||||
use config::define_config;
|
use config::define_config;
|
||||||
|
@ -132,8 +132,8 @@ pub struct Player {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GameState {
|
pub struct GameState {
|
||||||
pub board: Board,
|
pub board: Goban,
|
||||||
pub past_positions: Vec<Board>,
|
pub past_positions: Vec<Goban>,
|
||||||
|
|
||||||
pub conversation: Vec<String>,
|
pub conversation: Vec<String>,
|
||||||
pub current_player: Color,
|
pub current_player: Color,
|
||||||
|
@ -148,7 +148,7 @@ pub struct GameState {
|
||||||
impl Default for GameState {
|
impl Default for GameState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
board: Board::new(),
|
board: Goban::new(),
|
||||||
past_positions: vec![],
|
past_positions: vec![],
|
||||||
conversation: vec![],
|
conversation: vec![],
|
||||||
current_player: Color::Black,
|
current_player: Color::Black,
|
||||||
|
@ -200,7 +200,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn current_player_remains_the_same_after_self_capture() {
|
fn current_player_remains_the_same_after_self_capture() {
|
||||||
let mut state = GameState::default();
|
let mut state = GameState::default();
|
||||||
state.board = Board::from_coordinates(
|
state.board = Goban::from_coordinates(
|
||||||
vec![
|
vec![
|
||||||
(Coordinate { column: 17, row: 0 }, Color::White),
|
(Coordinate { column: 17, row: 0 }, Color::White),
|
||||||
(Coordinate { column: 17, row: 1 }, Color::White),
|
(Coordinate { column: 17, row: 1 }, Color::White),
|
||||||
|
@ -221,7 +221,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn ko_rules_are_enforced() {
|
fn ko_rules_are_enforced() {
|
||||||
let mut state = GameState::default();
|
let mut state = GameState::default();
|
||||||
state.board = Board::from_coordinates(
|
state.board = Goban::from_coordinates(
|
||||||
vec![
|
vec![
|
||||||
(Coordinate { column: 7, row: 9 }, Color::White),
|
(Coordinate { column: 7, row: 9 }, Color::White),
|
||||||
(Coordinate { column: 8, row: 8 }, Color::White),
|
(Coordinate { column: 8, row: 8 }, Color::White),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::types::{Color, Size};
|
use crate::types::{Color, Size};
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{CoreRequest, PlayStoneRequest},
|
api::{CoreRequest, PlayStoneRequest},
|
||||||
Board, Coordinate,
|
Goban, Coordinate,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use typeshare::typeshare;
|
use typeshare::typeshare;
|
||||||
|
@ -80,8 +80,8 @@ impl BoardElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Board> for BoardElement {
|
impl From<&Goban> for BoardElement {
|
||||||
fn from(board: &Board) -> Self {
|
fn from(board: &Goban) -> Self {
|
||||||
let spaces: Vec<IntersectionElement> =
|
let spaces: Vec<IntersectionElement> =
|
||||||
(0..board.size.height)
|
(0..board.size.height)
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/>.
|
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.
|
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.
|
||||||
|
|
||||||
|
@ -22,4 +21,7 @@ The view model is primary over the view. It will construct the view, it can make
|
||||||
*/
|
*/
|
||||||
|
|
||||||
mod database_view_model;
|
mod database_view_model;
|
||||||
pub use database_view_model::DatabaseViewModel;
|
pub use database_view_model::DatabaseViewModel;
|
||||||
|
|
||||||
|
mod game_view_model;
|
||||||
|
pub use game_view_model::GameViewModel;
|
||||||
|
|
Loading…
Reference in New Issue