Lines
99.61 %
Functions
98.25 %
Branches
100 %
// This file is part of hnefatafl-copenhagen.
//
// hnefatafl-copenhagen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// hnefatafl-copenhagen 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 Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg(test)]
use std::{fmt, str::FromStr, time::Duration};
use crate::{
ai::{AI, AiBanal},
board::BoardSize,
game_tree::Tree,
};
use super::*;
use board::{Board, STARTING_POSITION_11X11};
use game::Game;
use play::Vertex;
use role::Role;
use status::Status;
fn assert_error_str<T: fmt::Debug>(result: anyhow::Result<T>, string: &str) {
if let Err(error) = result {
assert_eq!(error.to_string(), string);
}
#[test]
fn monte_carlo() {
let mut tree = Tree::new(Game::new_game(BoardSize::_11, None));
let depth = 80;
let (loops, _plays) = tree.monte_carlo_tree_search(Duration::from_secs(1), depth);
println!("{loops}");
#[ignore = "takes too long"]
fn monte_carlo_long() {
let depth = 40;
let (loops, _plays) = tree.monte_carlo_tree_search(Duration::from_secs(10), depth);
fn flood_fill_1() -> anyhow::Result<()> {
let board_1 = [
"...........",
".........X.",
".....O.....",
"....O.O....",
"....OKO....",
];
let game = game::Game {
board: board_1.try_into()?,
turn: Role::Defender,
..Default::default()
let vertex = Vertex::from_str("f1")?;
assert!(game.board.flood_fill_defender_wins(&vertex));
Ok(())
fn flood_fill_2() -> anyhow::Result<()> {
"....OO.....",
"....O......",
assert!(!game.board.flood_fill_defender_wins(&vertex));
// One
fn starting_position() -> anyhow::Result<()> {
let game = Game::default();
assert_eq!(game.board, STARTING_POSITION_11X11.try_into()?);
// Two
fn first_turn() {
assert_eq!(game.turn, Role::Attacker);
// Three
fn move_orthogonally_1() -> anyhow::Result<()> {
let board = [
"...X.......",
"X..O......X",
let mut game = game::Game {
board: board.try_into()?,
let mut result = game.read_line("play defender d4 d1");
assert!(result.is_err());
assert_error_str(result, "play: you have to play through empty locations");
result = game.read_line("play defender d4 d11");
result = game.read_line("play defender d4 a4");
result = game.read_line("play defender d4 k4");
fn move_orthogonally_2() -> anyhow::Result<()> {
"...O.......",
// Play a junk move:
let mut result = game.read_line("play defender junk d1");
assert_error_str(result, "invalid digit found in string");
result = game.read_line("play defender d4 junk");
// Diagonal play:
result = game.read_line("play defender d4 a3");
assert_error_str(result, "play: you can only play in a straight line");
// Play out of bounds:
result = game.read_line("play defender d4 m4");
assert_error_str(result, "play: the first letter is not a legal char");
result = game.read_line("play defender d4 d12");
assert_error_str(result, "play: invalid coordinate");
result = game.read_line("play defender d4 d0");
// Don't move:
result = game.read_line("play defender d4 d4");
assert_error_str(result, "play: you have to change location");
// Move all the way to the right:
let mut game_1 = game.clone();
game_1.read_line("play defender d4 a4")?;
// Move all the way to the left:
let mut game_2 = game.clone();
game_2.read_line("play defender d4 k4")?;
// Move all the way up:
let mut game_3 = game.clone();
game_3.read_line("play defender d4 d11")?;
// Move all the way down:
let mut game_4 = game.clone();
game_4.read_line("play defender d4 d1")?;
// Four
fn sandwich_capture_1() -> anyhow::Result<()> {
".XO.OX.....",
let board_2 = [
".X.X.X.....",
game.read_line("play attacker d2 d4")?;
assert_eq!(game.board, board_2.try_into()?);
fn sandwich_capture_2() -> anyhow::Result<()> {
"...K.......",
fn sandwich_capture_3() -> anyhow::Result<()> {
".X.........",
".....X.....",
game.read_line("play attacker b4 f4")?;
fn sandwich_capture_4() -> anyhow::Result<()> {
"..K........",
"..X........",
"..O........",
game.read_line("play defender c6 c4")?;
fn sandwich_capture_5() -> anyhow::Result<()> {
".....K.....",
".O.........",
game.read_line("play defender b4 f4")?;
fn sandwich_capture_6() -> anyhow::Result<()> {
game.read_line("play attacker c9 c11")?;
fn sandwich_capture_7() -> anyhow::Result<()> {
fn sandwich_capture_8() -> anyhow::Result<()> {
".O.O.......",
".OXO.......",
game.read_line("play attacker c3 c5")?;
fn shield_wall_1() -> anyhow::Result<()> {
"...OOO.....",
"...XXXO....",
"..O...O....",
let mut game_1 = game::Game {
game_1.read_line("play defender c3 c1")?;
assert_eq!(game_1.board, board_2.try_into()?);
let board_3 = [
let board_4 = [
let mut game_2 = game::Game {
board: board_3.try_into()?,
game_2.read_line("play defender c9 c11")?;
assert_eq!(game_2.board, board_4.try_into()?);
fn shield_wall_1_13() -> anyhow::Result<()> {
"...XXXO......",
"...OOO.......",
"..O..........",
".............",
"..O...O......",
game_1.read_line("play defender C11 C13")?;
fn shield_wall_2() -> anyhow::Result<()> {
"XO.........",
"O..........",
game_1.read_line("play defender c7 a7")?;
"........O..",
".........OX",
"..........O",
".........O.",
game_2.read_line("play defender i7 k7")?;
fn shield_wall_3() -> anyhow::Result<()> {
"........XX.",
".....X..OK.",
".......X.K.",
game_1.read_line("play attacker f1 h1")?;
".XX........",
".KO..X.....",
".K.X.......",
game_2.read_line("play attacker f1 d1")?;
fn shield_wall_4() -> anyhow::Result<()> {
game_1.read_line("play attacker f11 h11")?;
game_2.read_line("play attacker f11 d11")?;
fn shield_wall_5() -> anyhow::Result<()> {
"X..........",
"OX.........",
"KX.........",
game_1.read_line("play attacker a6 a4")?;
game_2.read_line("play attacker a6 a8")?;
fn shield_wall_6() -> anyhow::Result<()> {
"..........X",
".........XO",
".........XK",
game_1.read_line("play attacker k6 k4")?;
game_2.read_line("play attacker k6 k8")?;
fn shield_wall_7() -> anyhow::Result<()> {
"........X.O",
game.read_line("play attacker i10 j10")?;
game.read_line("play attacker i9 j9")?;
assert_eq!(game.board, board_4.try_into()?);
fn shield_wall_nope() -> anyhow::Result<()> {
// Five
fn kings_1() {
"KK.........",
let result: anyhow::Result<Board> = board.try_into();
assert_error_str(result, "You can only have one king!");
fn kings_2() -> anyhow::Result<()> {
let result = game.read_line("play attacker b11 a11");
assert_error_str(
result,
"play: only the king may move to a restricted square",
);
fn kings_3() -> anyhow::Result<()> {
"K..........",
let _board: Board = board_1.try_into()?;
let result: anyhow::Result<Board> = board_2.try_into();
assert_error_str(result, "Only the king is allowed on restricted squares!");
fn kings_4() -> anyhow::Result<()> {
"..........K",
fn kings_5() -> anyhow::Result<()> {
fn kings_6() -> anyhow::Result<()> {
fn kings_7() -> anyhow::Result<()> {
// Six
fn defender_wins_exit() -> anyhow::Result<()> {
let mut game_2 = game_1.clone();
game_1.read_line("play defender f1 k1")?;
assert_eq!(game_1.status, Status::DefenderWins);
game_2.read_line("play defender f1 a1")?;
assert_eq!(game_2.status, Status::DefenderWins);
game_1.read_line("play defender f11 k11")?;
game_2.read_line("play defender f11 a11")?;
fn defender_wins_escape_fort_1() -> anyhow::Result<()> {
"....XX.....",
game.read_line("play defender f10 f11")?;
assert_eq!(game.status, Status::DefenderWins);
game.read_line("play defender f2 f1")?;
"OOOX.......",
".KOX.......",
"OO.........",
game.read_line("play defender b6 a6")?;
".......XOOO",
".......XOK.",
".........OO",
game.read_line("play defender j6 k6")?;
fn defender_wins_escape_fort_1_13() -> anyhow::Result<()> {
"....OKO......",
"....O........",
"....OOO......",
"....XX.......",
game.read_line("play defender G11 G12")?;
fn defender_wins_escape_fort_2() -> anyhow::Result<()> {
"........XOO",
"........OK.",
".......X.OO",
assert_eq!(game.status, Status::Ongoing);
fn defender_wins_escape_fort_3() -> anyhow::Result<()> {
".......X.OK",
game.read_line("play defender k5 k6")?;
// Seven
fn kings_not_captured() -> anyhow::Result<()> {
".....KX....",
]
.try_into()?;
game.read_line("play attacker f8 f7")?;
assert_eq!(game.board, board_2);
fn kings_captured_1() -> anyhow::Result<()> {
"....XKX....",
assert_eq!(game.status, Status::AttackerWins);
fn kings_captured_2() -> anyhow::Result<()> {
game.read_line("play attacker f3 f4")?;
fn kings_captured_3() -> anyhow::Result<()> {
"....X......",
"...XKX.....",
game.read_line("play attacker e1 e2")?;
fn kings_captured_4() -> anyhow::Result<()> {
"K.X........",
game.read_line("play attacker c3 b3")?;
fn kings_captured_5() -> anyhow::Result<()> {
game.read_line("play attacker c2 b2")?;
fn kings_captured_surround_1() -> anyhow::Result<()> {
".....XXX...",
"....X...XX.",
".X...O....X",
"..X.......X",
".X.O...O..X",
".X..OK...X.",
"..X...O.X..",
"...XXX.X...",
"......X....",
game.read_line("play attacker b7 c7")?;
game.read_line("play defender f7 g7")?;
game.read_line("play attacker c7 d7")?;
fn kings_captured_surround_2() -> anyhow::Result<()> {
"..X..O....X",
// Eight
fn can_not_repeat_moves() -> anyhow::Result<()> {
let mut game = Game::default();
game.read_line("play attacker f2 f3")?;
game.read_line("play defender f4 g4")?;
game.read_line("play attacker f3 f2")?;
let result = game.read_line("play defender g4 f4");
assert_error_str(result, "play: you already reached that position");
// Nine
fn attacker_automatically_loses() -> anyhow::Result<()> {
game.read_line("play defender f1 f2")?;
fn defender_automatically_loses_1() -> anyhow::Result<()> {
".X..XKX....",
game.read_line("play attacker b1 b2")?;
fn defender_automatically_loses_2() -> anyhow::Result<()> {
".X..X.X....",
game.read_line("play attacker b2 b1")?;
fn exit_one_1() -> anyhow::Result<()> {
".X...K...X.",
assert!(!game.exit_one());
fn exit_one_2() -> anyhow::Result<()> {
assert!(game.exit_one());
fn exit_one_3() -> anyhow::Result<()> {
fn exit_one_4() -> anyhow::Result<()> {
fn closed_off_exits() -> anyhow::Result<()> {
let board: Board = [
"..X..K.....",
assert_eq!(board.closed_off_exits(), 1);
"..X....X...",
".X......X..",
"X........X.",
"X.........X",
".X.......X.",
"..X..K..X..",
assert_eq!(board.closed_off_exits(), 4);
".XX.....XX.",
".XX..K..XX.",
assert_eq!(board.closed_off_exits(), 0);
fn someone_wins() {
let mut ai: Box<dyn AI> = Box::new(AiBanal);
loop {
// Do nothing but play.
if ai.generate_move(&mut game).is_err() {
break;
assert!(game.status == Status::AttackerWins || game.status == Status::DefenderWins);