Compare commits
No commits in common. "d6b424d335184e27f6389a63f59badc633d63550" and "f5429340a1316f84159c939b540fe202af40be5e" have entirely different histories.
d6b424d335
...
f5429340a1
|
@ -19,9 +19,9 @@ You should have received a copy of the GNU General Public License along with On
|
||||||
// documenting) my code from almost a year ago.
|
// documenting) my code from almost a year ago.
|
||||||
//
|
//
|
||||||
use crate::{BoardError, Color, Size};
|
use crate::{BoardError, Color, Size};
|
||||||
use sgf::{GameNode, Move, MoveNode};
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Goban {
|
pub struct Goban {
|
||||||
/// The size of the board. Usually this is symetrical, but I have actually played a 5x25 game.
|
/// The size of the board. Usually this is symetrical, but I have actually played a 5x25 game.
|
||||||
|
@ -97,11 +97,9 @@ impl Goban {
|
||||||
pub fn from_coordinates(
|
pub fn from_coordinates(
|
||||||
coordinates: impl IntoIterator<Item = (Coordinate, Color)>,
|
coordinates: impl IntoIterator<Item = (Coordinate, Color)>,
|
||||||
) -> Result<Self, BoardError> {
|
) -> Result<Self, BoardError> {
|
||||||
coordinates
|
coordinates.into_iter().try_fold(Self::new(), |board, (coordinate, color)| {
|
||||||
.into_iter()
|
board.place_stone(coordinate, color)
|
||||||
.try_fold(Self::new(), |board, (coordinate, color)| {
|
})
|
||||||
board.place_stone(coordinate, color)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +144,7 @@ impl Goban {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|c| self.stone(c) == Some(color))
|
.filter(|c| self.stone(c) == Some(color))
|
||||||
.filter_map(|c| self.group(&c).map(|g| g.coordinates.clone()))
|
.filter_map(|c| self.group(&c).map(|g| g.coordinates.clone()))
|
||||||
|
|
||||||
// In fact, this last step actually connects the coordinates of those friendly groups
|
// In fact, this last step actually connects the coordinates of those friendly groups
|
||||||
// into a single large group.
|
// into a single large group.
|
||||||
.fold(HashSet::new(), |acc, set| {
|
.fold(HashSet::new(), |acc, set| {
|
||||||
|
@ -190,54 +189,7 @@ impl Goban {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a list of moves to the board and return the final board. The moves will be played as
|
fn stone(&self, coordinate: &Coordinate) -> Option<Color> {
|
||||||
/// though they are live moves played normally, but this function is for generating a board
|
|
||||||
/// state from a game record. All of the moves will be played in the order given. This does not
|
|
||||||
/// allow for the branching which is natural in a game review.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use otg_core::{Color, Size, Coordinate, Goban};
|
|
||||||
/// use cool_asserts::assert_matches;
|
|
||||||
/// use sgf::{GameNode, MoveNode, Move};
|
|
||||||
///
|
|
||||||
/// let goban = Goban::new();
|
|
||||||
/// let moves = vec![
|
|
||||||
/// GameNode::MoveNode(MoveNode::new(sgf::Color::Black, Move::Move("dd".to_owned()))),
|
|
||||||
/// GameNode::MoveNode(MoveNode::new(sgf::Color::White, Move::Move("pp".to_owned()))),
|
|
||||||
/// GameNode::MoveNode(MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned()))),
|
|
||||||
/// ];
|
|
||||||
/// let moves_: Vec<&GameNode> = moves.iter().collect();
|
|
||||||
/// let goban = goban.apply_moves(moves_).expect("the test to have valid moves");
|
|
||||||
///
|
|
||||||
/// assert_eq!(goban.stone(&Coordinate{ row: 3, column: 3 }), Some(Color::Black));
|
|
||||||
/// assert_eq!(goban.stone(&Coordinate{ row: 15, column: 15 }), Some(Color::White));
|
|
||||||
/// assert_eq!(goban.stone(&Coordinate{ row: 3, column: 15 }), Some(Color::Black));
|
|
||||||
/// ```
|
|
||||||
pub fn apply_moves<'a>(
|
|
||||||
self,
|
|
||||||
moves: impl IntoIterator<Item = &'a GameNode>,
|
|
||||||
) -> Result<Goban, BoardError> {
|
|
||||||
let mut s = self;
|
|
||||||
for m in moves.into_iter() {
|
|
||||||
let s = match m {
|
|
||||||
GameNode::MoveNode(node) => s = s.apply_move_node(node)?,
|
|
||||||
GameNode::SetupNode(_n) => unimplemented!("setup nodes aren't processed yet"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_move_node(self, m: &MoveNode) -> Result<Goban, BoardError> {
|
|
||||||
if let Some((row, column)) = m.mv.coordinate() {
|
|
||||||
self.place_stone(Coordinate { row, column }, Color::from(&m.color))
|
|
||||||
} else {
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stone(&self, coordinate: &Coordinate) -> Option<Color> {
|
|
||||||
self.groups
|
self.groups
|
||||||
.iter()
|
.iter()
|
||||||
.find(|g| g.contains(coordinate))
|
.find(|g| g.contains(coordinate))
|
||||||
|
|
|
@ -52,15 +52,6 @@ pub enum Color {
|
||||||
White,
|
White,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&sgf::Color> for Color {
|
|
||||||
fn from(c: &sgf::Color) -> Self {
|
|
||||||
match c {
|
|
||||||
sgf::Color::Black => Self::Black,
|
|
||||||
sgf::Color::White => Self::White,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Size {
|
pub struct Size {
|
||||||
pub width: u8,
|
pub width: u8,
|
||||||
|
|
|
@ -84,9 +84,9 @@ impl AppWindow {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_game_review(&self, game_record: GameRecord) {
|
pub fn open_game_review(&self, _game: GameRecord) {
|
||||||
let header = adw::HeaderBar::new();
|
let header = adw::HeaderBar::new();
|
||||||
let game_review = GameReview::new(self.core.clone(), game_record);
|
let game_review = GameReview::new(self.core.clone());
|
||||||
|
|
||||||
let layout = gtk::Box::builder()
|
let layout = gtk::Box::builder()
|
||||||
.orientation(gtk::Orientation::Vertical)
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
|
|
@ -44,7 +44,6 @@ use gtk::{
|
||||||
subclass::prelude::*,
|
subclass::prelude::*,
|
||||||
};
|
};
|
||||||
use image::io::Reader as ImageReader;
|
use image::io::Reader as ImageReader;
|
||||||
use otg_core::Color;
|
|
||||||
use std::{cell::RefCell, io::Cursor, rc::Rc};
|
use std::{cell::RefCell, io::Cursor, rc::Rc};
|
||||||
|
|
||||||
const WIDTH: i32 = 800;
|
const WIDTH: i32 = 800;
|
||||||
|
@ -141,112 +140,5 @@ impl Goban {
|
||||||
|
|
||||||
ctx.set_source_rgb(0.7, 0.7, 0.7);
|
ctx.set_source_rgb(0.7, 0.7, 0.7);
|
||||||
let _ = ctx.paint();
|
let _ = ctx.paint();
|
||||||
|
|
||||||
let board = self.imp().board_state.borrow();
|
|
||||||
|
|
||||||
ctx.set_source_rgb(0.1, 0.1, 0.1);
|
|
||||||
ctx.set_line_width(2.);
|
|
||||||
let hspace_between = ((width - 40) as f64) / ((board.size.width - 1) as f64);
|
|
||||||
let vspace_between = ((height - 40) as f64) / ((board.size.height - 1) as f64);
|
|
||||||
|
|
||||||
let pen = Pen {
|
|
||||||
x_offset: MARGIN as f64,
|
|
||||||
y_offset: MARGIN as f64,
|
|
||||||
hspace_between,
|
|
||||||
vspace_between,
|
|
||||||
};
|
|
||||||
|
|
||||||
(0..board.size.width).for_each(|col| {
|
|
||||||
ctx.move_to(
|
|
||||||
(MARGIN as f64) + (col as f64) * hspace_between,
|
|
||||||
MARGIN as f64,
|
|
||||||
);
|
|
||||||
ctx.line_to(
|
|
||||||
(MARGIN as f64) + (col as f64) * hspace_between,
|
|
||||||
(height as f64) - (MARGIN as f64),
|
|
||||||
);
|
|
||||||
let _ = ctx.stroke();
|
|
||||||
});
|
|
||||||
(0..board.size.height).for_each(|row| {
|
|
||||||
ctx.move_to(
|
|
||||||
MARGIN as f64,
|
|
||||||
(MARGIN as f64) + (row as f64) * vspace_between,
|
|
||||||
);
|
|
||||||
ctx.line_to(
|
|
||||||
(width - MARGIN) as f64,
|
|
||||||
(MARGIN as f64) + (row as f64) * vspace_between,
|
|
||||||
);
|
|
||||||
let _ = ctx.stroke();
|
|
||||||
});
|
|
||||||
vec![3, 9, 15].into_iter().for_each(|col| {
|
|
||||||
vec![3, 9, 15].into_iter().for_each(|row| {
|
|
||||||
pen.star_point(ctx, col, row);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Pen {
|
|
||||||
x_offset: f64,
|
|
||||||
y_offset: f64,
|
|
||||||
hspace_between: f64,
|
|
||||||
vspace_between: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pen {
|
|
||||||
fn star_point(&self, context: &cairo::Context, row: u8, col: u8) {
|
|
||||||
context.arc(
|
|
||||||
self.x_offset + (col as f64) * self.hspace_between,
|
|
||||||
self.y_offset + (row as f64) * self.vspace_between,
|
|
||||||
5.,
|
|
||||||
0.,
|
|
||||||
2. * std::f64::consts::PI,
|
|
||||||
);
|
|
||||||
let _ = context.fill();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stone(
|
|
||||||
&self,
|
|
||||||
context: &cairo::Context,
|
|
||||||
row: u8,
|
|
||||||
col: u8,
|
|
||||||
color: Color,
|
|
||||||
liberties: Option<u8>,
|
|
||||||
) {
|
|
||||||
match color {
|
|
||||||
Color::White => context.set_source_rgb(0.9, 0.9, 0.9),
|
|
||||||
Color::Black => context.set_source_rgb(0.0, 0.0, 0.0),
|
|
||||||
};
|
|
||||||
self.draw_stone(context, row, col);
|
|
||||||
|
|
||||||
if let Some(liberties) = liberties {
|
|
||||||
let stone_location = self.stone_location(row, col);
|
|
||||||
context.set_source_rgb(1., 0., 1.);
|
|
||||||
context.set_font_size(32.);
|
|
||||||
context.move_to(stone_location.0 - 10., stone_location.1 + 10.);
|
|
||||||
let _ = context.show_text(&format!("{}", liberties));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ghost_stone(&self, context: &cairo::Context, row: u8, col: u8, color: Color) {
|
|
||||||
match color {
|
|
||||||
Color::White => context.set_source_rgba(0.9, 0.9, 0.9, 0.5),
|
|
||||||
Color::Black => context.set_source_rgba(0.0, 0.0, 0.0, 0.5),
|
|
||||||
};
|
|
||||||
self.draw_stone(context, row, col);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_stone(&self, context: &cairo::Context, row: u8, col: u8) {
|
|
||||||
let radius = self.hspace_between / 2. - 2.;
|
|
||||||
let (x_loc, y_loc) = self.stone_location(row, col);
|
|
||||||
context.arc(x_loc, y_loc, radius, 0.0, 2.0 * std::f64::consts::PI);
|
|
||||||
let _ = context.fill();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stone_location(&self, row: u8, col: u8) -> (f64, f64) {
|
|
||||||
(
|
|
||||||
self.x_offset + (col as f64) * self.hspace_between,
|
|
||||||
self.y_offset + (row as f64) * self.vspace_between,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ You should have received a copy of the GNU General Public License along with On
|
||||||
|
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use sgf::GameRecord;
|
|
||||||
use crate::{components::Goban, CoreApi};
|
use crate::{components::Goban, CoreApi};
|
||||||
|
|
||||||
pub struct GameReviewPrivate {}
|
pub struct GameReviewPrivate {}
|
||||||
|
@ -51,11 +50,9 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameReview {
|
impl GameReview {
|
||||||
pub fn new(api: CoreApi, record: GameRecord) -> Self {
|
pub fn new(api: CoreApi) -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
|
|
||||||
let board_repr = otg_core::Goban::default();
|
|
||||||
|
|
||||||
let board = Goban::new(otg_core::Goban::default());
|
let board = Goban::new(otg_core::Goban::default());
|
||||||
/*
|
/*
|
||||||
s.attach(&board, 0, 0, 2, 2);
|
s.attach(&board, 0, 0, 2, 2);
|
||||||
|
|
|
@ -320,19 +320,19 @@ impl TryFrom<&parser::Node> for GameNode {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct MoveNode {
|
pub struct MoveNode {
|
||||||
pub id: Uuid,
|
id: Uuid,
|
||||||
pub color: Color,
|
color: Color,
|
||||||
pub mv: Move,
|
mv: Move,
|
||||||
pub children: Vec<GameNode>,
|
children: Vec<GameNode>,
|
||||||
|
|
||||||
pub time_left: Option<Duration>,
|
time_left: Option<Duration>,
|
||||||
pub moves_left: Option<usize>,
|
moves_left: Option<usize>,
|
||||||
pub name: Option<String>,
|
name: Option<String>,
|
||||||
pub evaluation: Option<Evaluation>,
|
evaluation: Option<Evaluation>,
|
||||||
pub value: Option<f64>,
|
value: Option<f64>,
|
||||||
pub comments: Option<String>,
|
comments: Option<String>,
|
||||||
pub annotation: Option<Annotation>,
|
annotation: Option<Annotation>,
|
||||||
pub unknown_props: Vec<(String, String)>,
|
unknown_props: Vec<(String, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MoveNode {
|
impl MoveNode {
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
mod date;
|
mod date;
|
||||||
|
|
||||||
mod game;
|
mod game;
|
||||||
pub use game::{GameNode, GameRecord, MoveNode};
|
|
||||||
|
|
||||||
mod parser;
|
mod parser;
|
||||||
pub use parser::Move;
|
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
pub use types::*;
|
|
||||||
|
|
||||||
pub use date::Date;
|
|
||||||
pub use parser::parse_collection;
|
|
||||||
use std::{fs::File, io::Read};
|
use std::{fs::File, io::Read};
|
||||||
|
pub use date::Date;
|
||||||
|
pub use game::GameRecord;
|
||||||
|
pub use parser::parse_collection;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
pub use types::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -71,8 +67,7 @@ impl From<nom::error::Error<&str>> for ParseError {
|
||||||
/// still be kept as valid.
|
/// still be kept as valid.
|
||||||
pub fn parse_sgf(input: &str) -> Result<Vec<Result<GameRecord, game::GameError>>, Error> {
|
pub fn parse_sgf(input: &str) -> Result<Vec<Result<GameRecord, game::GameError>>, Error> {
|
||||||
let (_, games) = parse_collection::<nom::error::VerboseError<&str>>(&input)?;
|
let (_, games) = parse_collection::<nom::error::VerboseError<&str>>(&input)?;
|
||||||
let games = games
|
let games = games.into_iter()
|
||||||
.into_iter()
|
|
||||||
.map(|game| GameRecord::try_from(&game))
|
.map(|game| GameRecord::try_from(&game))
|
||||||
.collect::<Vec<Result<GameRecord, game::GameError>>>();
|
.collect::<Vec<Result<GameRecord, game::GameError>>>();
|
||||||
|
|
||||||
|
@ -82,9 +77,7 @@ pub fn parse_sgf(input: &str) -> Result<Vec<Result<GameRecord, game::GameError>>
|
||||||
/// Given a path, parse all of the games stored in that file.
|
/// Given a path, parse all of the games stored in that file.
|
||||||
///
|
///
|
||||||
/// See also `parse_sgf`
|
/// See also `parse_sgf`
|
||||||
pub fn parse_sgf_file(
|
pub fn parse_sgf_file(path: &std::path::Path) -> Result<Vec<Result<GameRecord, game::GameError>>, Error> {
|
||||||
path: &std::path::Path,
|
|
||||||
) -> Result<Vec<Result<GameRecord, game::GameError>>, Error> {
|
|
||||||
let mut file = File::open(path).unwrap();
|
let mut file = File::open(path).unwrap();
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let _ = file.read_to_string(&mut text);
|
let _ = file.read_to_string(&mut text);
|
||||||
|
|
|
@ -295,26 +295,6 @@ pub enum Move {
|
||||||
Pass,
|
Pass,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Move {
|
|
||||||
pub fn coordinate(&self) -> Option<(u8, u8)> {
|
|
||||||
match self {
|
|
||||||
Move::Pass => None,
|
|
||||||
Move::Move(s) => {
|
|
||||||
if s.len() == 2 {
|
|
||||||
let mut parts = s.chars();
|
|
||||||
let row_char = parts.next().unwrap();
|
|
||||||
let row = row_char as u8 - 'a' as u8;
|
|
||||||
let column_char = parts.next().unwrap();
|
|
||||||
let column = column_char as u8 - 'a' as u8;
|
|
||||||
Some((row, column))
|
|
||||||
} else {
|
|
||||||
unimplemented!("moves must contain exactly two characters");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// KO
|
// KO
|
||||||
// MN
|
// MN
|
||||||
// N
|
// N
|
||||||
|
@ -1204,14 +1184,6 @@ k<. Hard line breaks are all other linebreaks.",
|
||||||
);
|
);
|
||||||
parse_tree::<nom::error::VerboseError<&str>>(&data).unwrap();
|
parse_tree::<nom::error::VerboseError<&str>>(&data).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_can_convert_moves_to_coordinates() {
|
|
||||||
assert_eq!(Move::Pass.coordinate(), None);
|
|
||||||
assert_eq!(Move::Move("dd".to_owned()).coordinate(), Some((3, 3)));
|
|
||||||
assert_eq!(Move::Move("jj".to_owned()).coordinate(), Some((9, 9)));
|
|
||||||
assert_eq!(Move::Move("pp".to_owned()).coordinate(), Some((15, 15)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in New Issue