rival 0.1.0

A framework for creating computer players for turn-based games
Documentation
use std::{fmt, ops};

use crate::game::Game;

#[derive(Clone, Eq, PartialEq, Hash)]
pub struct TicTacToe {
    turn: Symbol,
    field: [[Option<Symbol>; 3]; 3],
}

impl TicTacToe {
    pub fn new() -> Self {
        TicTacToe {
            turn: Symbol::O,
            field: [[None; 3]; 3],
        }
    }
}

#[derive(Copy, Clone, Eq, PartialEq, Hash)]
enum Symbol {
    X = 0,
    O = 1,
}

impl ops::Not for Symbol {
    type Output = Symbol;

    fn not(self) -> Self::Output {
        match self {
            Symbol::X => Symbol::O,
            Symbol::O => Symbol::X,
        }
    }
}

#[derive(Clone)]
pub struct Place {
    x: usize,
    y: usize,
}

impl Game<2> for TicTacToe {
    type Move = Place;

    const DEPTH: u32 = 9;

    fn turn(&self) -> usize {
        self.turn as usize
    }

    fn value(&self) -> [i32; 2] {
        let triplets = vec![
            ((0, 0), (0, 1), (0, 2)),
            ((1, 0), (1, 1), (1, 2)),
            ((2, 0), (2, 1), (2, 2)),
            ((0, 0), (1, 0), (2, 0)),
            ((0, 1), (1, 1), (2, 1)),
            ((0, 2), (1, 2), (2, 2)),
            ((0, 0), (1, 1), (2, 2)),
            ((0, 2), (1, 1), (2, 0))
        ];
        let winners: Vec<Symbol> = triplets
            .into_iter()
            .flat_map(|((x1, y1), (x2, y2), (x3, y3))|
                (self.field[x1][y1].is_some() &&
                    self.field[x1][y1] == self.field[x2][y2] &&
                    self.field[x2][y2] == self.field[x3][y3])
                    .then(|| self.field[x1][y1].unwrap())
            )
            .collect();
        if winners.is_empty() {
            [0, 0]
        } else {
            match winners[0] {
                Symbol::X => [1, -1],
                Symbol::O => [-1, 1]
            }
        }
    }

    fn moves(&self) -> Vec<Place> {
        if self.value()[0] != 0 {
            Vec::new()
        } else {
            self.field
                .iter()
                .enumerate()
                .flat_map(|(x, row)| row

                    .iter()
                    .enumerate()
                    .flat_map(move |(y, symbol)| symbol.is_none().then(|| Place { x, y }))
                )
                .collect()
        }
    }

    fn perform(&mut self, action: &Place) {
        if self.field[action.x][action.y].replace(self.turn).take().is_some() {
            panic!("There is already a symbol here")
        };
        self.turn = !self.turn;
    }

    fn revert(&mut self, action: &Place) {
        self.turn = !self.turn;
        self.field[action.x][action.y].take().expect("There is no symbol here");
    }
}

impl fmt::Display for Symbol {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Symbol::X => write!(f, "X"),
            Symbol::O => write!(f, "O"),
        }
    }
}

impl fmt::Display for TicTacToe {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(
            f,
            " {} | {} | {} \n---+---+---\n {} | {} | {} \n---+---+---\n {} | {} | {} ",
            self.field[0][0].map(|s| format!("{}", s)).unwrap_or(" ".to_owned()),
            self.field[1][0].map(|s| format!("{}", s)).unwrap_or(" ".to_owned()),
            self.field[2][0].map(|s| format!("{}", s)).unwrap_or(" ".to_owned()),
            self.field[0][1].map(|s| format!("{}", s)).unwrap_or(" ".to_owned()),
            self.field[1][1].map(|s| format!("{}", s)).unwrap_or(" ".to_owned()),
            self.field[2][1].map(|s| format!("{}", s)).unwrap_or(" ".to_owned()),
            self.field[0][2].map(|s| format!("{}", s)).unwrap_or(" ".to_owned()),
            self.field[1][2].map(|s| format!("{}", s)).unwrap_or(" ".to_owned()),
            self.field[2][2].map(|s| format!("{}", s)).unwrap_or(" ".to_owned()),
        )
    }
}