Search code examples
rusttic-tac-toeminimax

Tic Tac Toe - Minimax


I'm trying to build a tic-tac-toe game using minimax algorithm with rust. And I'm stuck. I tried to write a rust code based on the psudeo code on the wikipedia page. https://en.wikipedia.org/wiki/Minimax. However, it didn't work. Ai always makes the first possible move. I would be glad if you could help me.

In main.rs

fn main() {
    let mut g = Game::new();

    while g.game_state() == Game_State::Continuous {
        g.print();
        println!("{}", minimax(&g));

        if g.turn == Player::Cross {
            g.take_input();
        }
        else {
            g = best_move(&g);
        }
    }

    g.print();

    if let Game_State::Win(Player::None) = g.game_state() {
        println!("Draw");
    }
    else {
        g.print_winner();
    }
}

In ai.rs

pub fn child_nodes(game: &Game) -> Vec<Game> {
    let mut children: Vec<Game> = Vec::new();
    
    for r in 0..3 {
        for c in 0..3 {
            if game.grid[r][c] == Player::None {
                let mut child = game.clone();
                child.grid[r][c] = game.turn;
                child.turn = reverse_player(child.turn);
                children.push(child);
            }
        }
    }

    return children;
}

pub fn minimax(game: &Game) -> isize {
    match game.game_state() {
        Game_State::Win(winner) => to_scor(winner),
        Game_State::Continuous => {
            use std::cmp::{min, max};

            let children_vec = child_nodes(&game);
            let mut score: isize;

            if game.turn == Player::Cross {
                score = -2;

                for i in &children_vec {
                    score = max(score, minimax(i));
                }
                    
            }
            else {
                score = 2;
            
                for i in &children_vec {
                    score = min(score, minimax(i));
                }
            }

            return score;
        }
    }
}

pub fn best_move(game: &Game) -> Game {
    let children = child_nodes(game);
    let mut values: Vec<isize> = Vec::new();

    for i in 0..children.len() {
        values.push(minimax(&children[i]));
    }

    let mut index: usize = 0;
    let iter = values.iter().enumerate();

    if game.turn == Player::Cross {
        if let Option::Some(t) = iter.max() {
            index = t.0;
        }
    }
    else if game.turn == Player::Circle {
        if let Option::Some(t) = iter.min() {
            index = t.0;
        }
    }

    let best_pos = children[index];
    best_pos
}

pub fn to_scor(x: Player) -> isize {
    match x {
        Player::Cross => 1,
        Player::Circle => -1,
        Player::None => 0
    }
} 

Solution

  • .enumerate() returns an iterator over tuples, and .max() and .min() on an iterator of tuples will compare the tuples - that is, (1, x) is always considered to be less than (2, y) for any values of x and y. This can be demonstrated with this snippet:

    fn main() {
        let v = vec![3, 1, 2, 5, 3, 6, 7, 2];
        println!("{:?}", v.iter().enumerate().min());
        println!("{:?}", v.iter().enumerate().max());
    }
    

    which prints:

    Some((0, 3))
    Some((7, 2))
    

    which are just the first and last elements of the list (and not the minimum or maximum elements).

    However, as shown here, you can use max_by to use your own function to compare the tuples.