Set up the game review page along with #229
|
@ -114,6 +114,35 @@ impl GameRecord {
|
||||||
children: vec![],
|
children: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a list of moves which constitute the main line of the game. This is the game as it
|
||||||
|
/// was actually played out, and by convention consists of the first node in each list of
|
||||||
|
/// children.
|
||||||
|
pub fn mainline<'a>(&'a self) -> Vec<&'a GameNode> {
|
||||||
|
let mut moves: Vec<&GameNode> = vec![];
|
||||||
|
|
||||||
|
let mut next = self.children.get(0);
|
||||||
|
while let Some(node) = next {
|
||||||
|
// Given that I know that I have a node, and I know that I'm going to push a reference
|
||||||
|
// to it onto my final list, I want to get the first of its children. And I want to
|
||||||
|
// keep doing that until there are no more first children.
|
||||||
|
//
|
||||||
|
// Just going to push references onto the list. No need to copy the nodes for this.
|
||||||
|
//
|
||||||
|
// Pushing a reference onto the list implicitely clones the reference, but not the data
|
||||||
|
// it is pointing to. This means that each time through the loop, `next` points to
|
||||||
|
// something else. This isn't being described very well, though, so it's worth
|
||||||
|
// reviewing in the future.
|
||||||
|
moves.push(node);
|
||||||
|
|
||||||
|
next = match node {
|
||||||
|
GameNode::MoveNode(node) => node.children.get(0),
|
||||||
|
GameNode::SetupNode(node) => node.children.get(0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
moves
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node for GameRecord {
|
impl Node for GameRecord {
|
||||||
|
@ -666,6 +695,51 @@ mod setup_node_tests {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod path_test {
|
mod path_test {
|
||||||
|
use super::*;
|
||||||
|
use cool_asserts::assert_matches;
|
||||||
|
use parser::parse_collection;
|
||||||
|
use std::{fs::File, io::Read};
|
||||||
|
|
||||||
|
fn with_text(text: &str, f: impl FnOnce(Vec<GameRecord>)) {
|
||||||
|
let (_, games) = parse_collection::<nom::error::VerboseError<&str>>(text).unwrap();
|
||||||
|
let games = games
|
||||||
|
.into_iter()
|
||||||
|
.map(|game| GameRecord::try_from(&game).expect("game to parse"))
|
||||||
|
.collect::<Vec<GameRecord>>();
|
||||||
|
f(games);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_file(path: &std::path::Path, f: impl FnOnce(Vec<GameRecord>)) {
|
||||||
|
let mut file = File::open(path).unwrap();
|
||||||
|
let mut text = String::new();
|
||||||
|
let _ = file.read_to_string(&mut text);
|
||||||
|
with_text(&text, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn returns_the_mainline_of_a_game_without_branches() {
|
||||||
|
with_file(
|
||||||
|
std::path::Path::new("test_data/2020 USGO DDK, Round 1.sgf"),
|
||||||
|
|games| {
|
||||||
|
let game = &games[0];
|
||||||
|
|
||||||
|
let moves = game.mainline();
|
||||||
|
assert_matches!(moves[0], GameNode::MoveNode(node) => {
|
||||||
|
assert_eq!(node.color, Color::Black);
|
||||||
|
assert_eq!(node.mv, Move::Move("pp".to_owned()));
|
||||||
|
});
|
||||||
|
assert_matches!(moves[1], GameNode::MoveNode(node) => {
|
||||||
|
assert_eq!(node.color, Color::White);
|
||||||
|
assert_eq!(node.mv, Move::Move("dp".to_owned()));
|
||||||
|
});
|
||||||
|
assert_matches!(moves[2], GameNode::MoveNode(node) => {
|
||||||
|
assert_eq!(node.color, Color::Black);
|
||||||
|
assert_eq!(node.mv, Move::Move("pd".to_owned()));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[ignore]
|
#[ignore]
|
||||||
#[test]
|
#[test]
|
||||||
fn returns_empty_list_if_no_game_nodes() {
|
fn returns_empty_list_if_no_game_nodes() {
|
||||||
|
|
Loading…
Reference in New Issue