Set up the game review page along with #229

Merged
savanni merged 24 commits from otg/game-review into main 2024-03-31 23:37:51 +00:00
1 changed files with 74 additions and 0 deletions
Showing only changes of commit 642351f248 - Show all commits

View File

@ -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() {