├── .gitignore
├── src
├── strategies
│ ├── mod.rs
│ ├── random.rs
│ └── negamax.rs
├── lib.rs
├── util.rs
└── interface.rs
├── .travis.yml
├── .project
├── Cargo.toml
├── LICENSE
├── tests
└── ttt.rs
├── benches
└── negamax.rs
├── README.md
└── examples
└── ttt.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | Cargo.lock
2 | target
3 |
--------------------------------------------------------------------------------
/src/strategies/mod.rs:
--------------------------------------------------------------------------------
1 | //! Strategy implementations.
2 |
3 | pub mod negamax;
4 | pub mod random;
5 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | extern crate rand;
2 |
3 | pub mod interface;
4 | pub mod strategies;
5 | pub mod util;
6 |
7 | pub use interface::{Evaluation, Evaluator, Game, Move, Player, Strategy, Winner};
8 | pub use strategies::negamax::{Negamax, Options};
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: rust
2 | sudo: false
3 | rust:
4 | - stable
5 | - beta
6 | - nightly
7 | matrix:
8 | allow_failures:
9 | - rust: nightly
10 | script:
11 | - cargo clean
12 | - cargo build
13 | - cargo package
14 | - cargo test
15 | - cargo doc
16 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | minimax
4 |
5 |
6 |
7 |
8 |
9 | com.github.rustdt.ide.core.Builder
10 |
11 |
12 |
13 |
14 |
15 | com.github.rustdt.ide.core.nature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "minimax"
3 | version = "0.0.2"
4 | authors = [ "Samuel Fredrickson " ]
5 | description = "Generic implementations of Minimax."
6 | documentation = "http://kinghajj.github.io/doc/minimax/"
7 | repository = "http://github.com/kinghajj/minimax-rs.git"
8 | readme = "README.md"
9 | keywords = ["ai", "game", "minimax", "negamax"]
10 | license = "MIT"
11 |
12 | [dependencies]
13 | rand = "0.3.*"
14 |
15 | [profile.test]
16 | opt-level = 3
17 |
--------------------------------------------------------------------------------
/src/strategies/random.rs:
--------------------------------------------------------------------------------
1 | //! A strategy that randomly chooses a move, for use in tests.
2 |
3 | use super::super::interface::*;
4 | use rand;
5 | use rand::Rng;
6 |
7 | pub struct Random {
8 | rng: rand::ThreadRng,
9 | }
10 |
11 | impl Random {
12 | pub fn new() -> Random {
13 | Random { rng: rand::thread_rng() }
14 | }
15 | }
16 |
17 | impl Strategy for Random
18 | where G::M: Copy {
19 | fn choose_move(&mut self, s: &G::S, p: Player) -> Option {
20 | let mut moves: [Option; 100] = [None; 100];
21 | match G::generate_moves(s, p, &mut moves) {
22 | 0 => None,
23 | num_moves => Some(moves[self.rng.gen_range(0, num_moves)].unwrap()),
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Samuel Fredrickson
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/src/util.rs:
--------------------------------------------------------------------------------
1 | //! Utility functions for testing, and tests.
2 |
3 | use super::interface;
4 | use super::interface::Move;
5 | use std::default::Default;
6 |
7 | /// Play a complete, new game with players using the two provided strategies.
8 | ///
9 | /// The first strategy will be `Player::Computer`, the other `Player::Opponent`.
10 | /// Returns result of the game.
11 | pub fn battle_royale(s1: &mut S1, s2: &mut S2) -> interface::Winner
12 | where G: interface::Game,
13 | G::S: Default,
14 | S1: interface::Strategy,
15 | S2: interface::Strategy
16 | {
17 | let mut state = G::S::default();
18 | let mut strategies: Vec<(interface::Player, &mut interface::Strategy)> = vec![
19 | (interface::Player::Computer, s1),
20 | (interface::Player::Opponent, s2),
21 | ];
22 | let mut s = 0;
23 | while G::get_winner(&state).is_none() {
24 | let (p, ref mut strategy) = strategies[s];
25 | match strategy.choose_move(&mut state, p) {
26 | Some(m) => m.apply(&mut state),
27 | None => break,
28 | }
29 | s = 1 - s;
30 | }
31 | G::get_winner(&state).unwrap()
32 | }
33 |
--------------------------------------------------------------------------------
/tests/ttt.rs:
--------------------------------------------------------------------------------
1 | extern crate minimax;
2 |
3 | #[path="../examples/ttt.rs"]
4 | mod ttt;
5 |
6 | use minimax::util::battle_royale;
7 |
8 | // Ensure that two players using negamax always results in a draw.
9 | #[test]
10 | fn test_ttt_negamax_always_draws() {
11 | use minimax::strategies::negamax::{Negamax, Options};
12 | let mut s1 = Negamax::::new(Options { max_depth: 10 });
13 | let mut s2 = Negamax::::new(Options { max_depth: 10 });
14 | for _ in 0..100 {
15 | assert!(battle_royale(&mut s1, &mut s2) == minimax::Winner::Draw)
16 | }
17 | }
18 |
19 | // Ensure that a player using negamax against a random one always results in
20 | // either a draw or a win for the former player.
21 | #[test]
22 | fn test_ttt_negamax_vs_random_always_wins_or_draws() {
23 | use minimax::strategies::negamax::{Negamax, Options};
24 | use minimax::strategies::random::Random;
25 | let mut s1 = Negamax::::new(Options { max_depth: 10 });
26 | let mut s2 = Random::new();
27 | for _ in 0..100 {
28 | assert!(battle_royale(&mut s1, &mut s2) !=
29 | minimax::Winner::Competitor(minimax::Player::Opponent))
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/benches/negamax.rs:
--------------------------------------------------------------------------------
1 | #![feature(test)]
2 | extern crate minimax;
3 | extern crate test;
4 | use test::Bencher;
5 | use minimax::*;
6 |
7 | #[derive(Clone)]
8 | pub struct Board;
9 |
10 | #[derive(Copy, Clone)]
11 | pub struct Place;
12 |
13 | pub struct Eval;
14 |
15 | pub struct Noop;
16 |
17 | impl Move for Place {
18 | type G = Noop;
19 | fn apply(&self, _: &mut Board) {
20 | }
21 | fn undo(&self, _: &mut Board) {
22 | }
23 | }
24 |
25 | impl Game for Noop {
26 | type S = Board;
27 | type M = Place;
28 |
29 | fn generate_moves(_: &Board, _: Player, ms: &mut [Option]) -> usize {
30 | const NUM_MOVES: usize = 4;
31 | for m in ms.iter_mut().take(NUM_MOVES) {
32 | *m = Some(Place);
33 | }
34 | ms[NUM_MOVES] = None;
35 | NUM_MOVES
36 | }
37 |
38 | fn get_winner(_: &Board) -> Option {
39 | None
40 | }
41 | }
42 |
43 | impl Evaluator for Eval {
44 | type G = Noop;
45 |
46 | fn evaluate(_: &Board, _: Option) -> Evaluation {
47 | Evaluation::Score(0)
48 | }
49 | }
50 |
51 | #[bench]
52 | fn bench_negamax(b: &mut Bencher) {
53 | let board = Board;
54 | let mut s = Negamax::::new(Options { max_depth: 10 });
55 | b.iter(|| s.choose_move(&board, Player::Computer));
56 | }
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # minimax-rs - Generic implementations of Minimax in Rust.
2 |
3 | [](https://travis-ci.org/kinghajj/minimax-rs) [](https://crates.io/crates/minimax)
4 |
5 | ## About
6 |
7 | This library provides interfaces that describe:
8 |
9 | 1. the rules for two-player, perfect-knowledge games;
10 | 2. methods of evaluating particular game states for a player; and
11 | 3. strategies for choosing moves for a player.
12 |
13 | The eventual goal is to have multiple proper strategies, so that any combination
14 | of evaluators and strategies can be tested against each other. Currently, only
15 | a basic alpha-beta pruning Negamax strategy is implemented.
16 |
17 | ## Example
18 |
19 | The `ttt` module contains an implementation of Tic-Tac-Toe, demonstrating how to
20 | use the game and evaluation interfaces. `test` shows how to use strategies.
21 |
22 | ## License
23 |
24 | Copyright (c) 2015 Samuel Fredrickson
25 |
26 | Permission is hereby granted, free of charge, to any person obtaining a copy
27 | of this software and associated documentation files (the "Software"), to deal
28 | in the Software without restriction, including without limitation the rights
29 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
30 | copies of the Software, and to permit persons to whom the Software is
31 | furnished to do so, subject to the following conditions:
32 |
33 | The above copyright notice and this permission notice shall be included in
34 | all copies or substantial portions of the Software.
35 |
36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
37 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
38 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
39 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
40 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
41 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
42 | THE SOFTWARE.
43 |
--------------------------------------------------------------------------------
/src/strategies/negamax.rs:
--------------------------------------------------------------------------------
1 | //! An implementation of Negamax.
2 | //!
3 | //! Currently, only the basic alpha-pruning variant is implemented. Further work
4 | //! could add advanced features, like history and/or transposition tables. This
5 | //! picks randomly among the "best" moves, so that it's non-deterministic.
6 |
7 | use super::super::interface::*;
8 | use rand;
9 | use rand::Rng;
10 | use std::cmp::max;
11 | use std::marker::PhantomData;
12 |
13 | fn negamax(s: &mut ::S,
14 | depth: usize,
15 | mut alpha: Evaluation,
16 | beta: Evaluation,
17 | p: Player)
18 | -> Evaluation
19 | where <::G as Game>::M: Copy
20 | {
21 | let maybe_winner = E::G::get_winner(s);
22 | if depth == 0 || maybe_winner.is_some() {
23 | return p * E::evaluate(s, maybe_winner);
24 | }
25 | let mut moves = [None; 100];
26 | E::G::generate_moves(s, p, &mut moves);
27 | let mut best = Evaluation::Worst;
28 | for m in moves.iter().take_while(|om| om.is_some()).map(|om| om.unwrap()) {
29 | m.apply(s);
30 | let value = -negamax::(s, depth - 1, -beta, -alpha, -p);
31 | m.undo(s);
32 | best = max(best, value);
33 | alpha = max(alpha, value);
34 | if alpha >= beta {
35 | break
36 | }
37 | }
38 | best
39 | }
40 |
41 | /// Options to use for the `Negamax` engine.
42 | pub struct Options {
43 | /// The maximum depth within the game tree.
44 | pub max_depth: usize,
45 | }
46 |
47 | pub struct Negamax {
48 | opts: Options,
49 | rng: rand::ThreadRng,
50 | _eval: PhantomData,
51 | }
52 |
53 | impl Negamax {
54 | pub fn new(opts: Options) -> Negamax {
55 | Negamax {
56 | opts: opts,
57 | rng: rand::thread_rng(),
58 | _eval: PhantomData,
59 | }
60 | }
61 | }
62 |
63 | impl Strategy for Negamax
64 | where ::S: Clone,
65 | ::M: Copy {
66 | fn choose_move(&mut self, s: &::S, p: Player) -> Option<::M> {
67 | let mut best = Evaluation::Worst;
68 | let mut moves = [None; 100];
69 | E::G::generate_moves(s, p, &mut moves);
70 | let mut candidate_moves = Vec::new();
71 | let mut s_clone = s.clone();
72 | for m in moves.iter().take_while(|m| m.is_some()).map(|m| m.unwrap()) {
73 | // determine value for this move
74 | m.apply(&mut s_clone);
75 | let value = -negamax::(&mut s_clone,
76 | self.opts.max_depth,
77 | Evaluation::Worst,
78 | Evaluation::Best,
79 | -p);
80 | m.undo(&mut s_clone);
81 | // this move is a candidate move
82 | if value == best {
83 | candidate_moves.push(m);
84 | // this move is better than any previous, so it's the sole candidate
85 | } else if value > best {
86 | candidate_moves.clear();
87 | candidate_moves.push(m);
88 | best = value;
89 | }
90 | }
91 | if candidate_moves.is_empty() {
92 | None
93 | } else {
94 | Some(candidate_moves[self.rng.gen_range(0, candidate_moves.len())])
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/interface.rs:
--------------------------------------------------------------------------------
1 | //! The common structures and traits.
2 |
3 | use std::ops;
4 |
5 | /// A competitor within a game.
6 | ///
7 | /// For simplicity, only two players are supported. Their values correspond to
8 | /// the "color" parameter in Negamax.
9 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10 | #[repr(i8)]
11 | pub enum Player {
12 | Computer = 1,
13 | Opponent = -1,
14 | }
15 |
16 | /// Negating a player results in the opposite one.
17 | impl ops::Neg for Player {
18 | type Output = Player;
19 | #[inline]
20 | fn neg(self) -> Player {
21 | match self {
22 | Player::Computer => Player::Opponent,
23 | Player::Opponent => Player::Computer,
24 | }
25 | }
26 | }
27 |
28 | /// An assessment of a game state from a particular player's perspective.
29 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
30 | pub enum Evaluation {
31 | /// An absolutely disastrous outcome, e.g. a loss.
32 | Worst,
33 | /// An outcome with some score. Higher values mean a more favorable state.
34 | Score(i64),
35 | /// An absolutely wonderful outcome, e.g. a win.
36 | Best,
37 | }
38 |
39 | /// Negating an evaluation results in the corresponding one from the other
40 | /// player's persective.
41 | impl ops::Neg for Evaluation {
42 | type Output = Evaluation;
43 | #[inline]
44 | fn neg(self) -> Evaluation {
45 | match self {
46 | Evaluation::Worst => Evaluation::Best,
47 | Evaluation::Score(s) => Evaluation::Score(-s),
48 | Evaluation::Best => Evaluation::Worst,
49 | }
50 | }
51 | }
52 |
53 | /// Multiplying a player and an evaluation negates the latter iff the former
54 | /// is `Opponent`.
55 | impl ops::Mul for Player {
56 | type Output = Evaluation;
57 | #[inline]
58 | fn mul(self, e: Evaluation) -> Evaluation {
59 | match self {
60 | Player::Computer => e,
61 | Player::Opponent => -e,
62 | }
63 | }
64 | }
65 |
66 | /// Evaluates a game's positions.
67 | ///
68 | /// The methods are defined recursively, so that implementing one is sufficient.
69 | pub trait Evaluator {
70 | /// The type of game that can be evaluated.
71 | type G: Game;
72 | /// Evaluate the state from the persective of `Player::Computer`.
73 | #[inline]
74 | fn evaluate(s: &::S, mw: Option) -> Evaluation {
75 | Self::evaluate_for(s, mw, Player::Computer)
76 | }
77 |
78 | /// Evaluate the state from the given player's persective.
79 | #[inline]
80 | fn evaluate_for(s: &::S, mw: Option, p: Player) -> Evaluation {
81 | p * Self::evaluate(s, mw)
82 | }
83 | }
84 |
85 | /// Defines how a move affects the game state.
86 | ///
87 | /// A move is able to change initial `Game` state, as well as revert the state.
88 | /// This allows the game tree to be searched with a constant amount of space.
89 | pub trait Move {
90 | /// The type of game that the move affects.
91 | type G: Game;
92 | /// Change the state of `S` so that the move is applied.
93 | #[inline]
94 | fn apply(&self, &mut ::S);
95 | /// Revert the state of `S` so that the move is undone.
96 | #[inline]
97 | fn undo(&self, &mut ::S);
98 | }
99 |
100 | /// The result of playing a game until it finishes.
101 | #[derive(Debug, Copy, Clone, PartialEq, Eq)]
102 | pub enum Winner {
103 | /// A player won.
104 | Competitor(Player),
105 | /// Nobody won.
106 | Draw,
107 | }
108 |
109 | /// Defines the rules for a two-player, perfect-knowledge game.
110 | ///
111 | /// A game ties together types for the state and moves, generates the possible
112 | /// moves from a particular state, and determines whether a state is terminal.
113 | pub trait Game : Sized {
114 | /// The type of the game state.
115 | type S;
116 | /// The type of game moves.
117 | type M: Move;
118 |
119 | /// Generate moves for a player at the given state. After finishing, the
120 | /// next entry in the slice should be set to `None` to indicate the end.
121 | /// Returns the number of moves generated.
122 | ///
123 | /// Currently, there's a deficiency that all strategies assume that at most
124 | /// 100 moves may be generated for any position, which allows the underlying
125 | /// memory for the slice to be a stack-allocated array. One stable, this
126 | /// trait will be extended with an associated constant to specify the
127 | /// maximum number of moves.
128 | #[inline]
129 | fn generate_moves(&Self::S, Player, &mut [Option]) -> usize;
130 |
131 | /// Returns `Some(Competitor(winning_player))` if there's a winner,
132 | /// `Some(Draw)` if the state is terminal without a winner, and `None` if
133 | /// the state is non-terminal.
134 | fn get_winner(&Self::S) -> Option;
135 | }
136 |
137 | /// Defines a method of choosing a move for either player in a any game.
138 | pub trait Strategy {
139 | fn choose_move(&mut self, &G::S, Player) -> Option;
140 | }
141 |
--------------------------------------------------------------------------------
/examples/ttt.rs:
--------------------------------------------------------------------------------
1 | //! A definition of the game Tic-Tac-Toe using the library, for use in tests.
2 | //!
3 | //! For example, playing a correctly-implemented strategy against itself should
4 | //! always result in a draw; and playing such a strategy against one that picks
5 | //! moves randomly should always result in a win or draw.
6 | #![allow(dead_code)]
7 |
8 | extern crate minimax;
9 |
10 | use std::default::Default;
11 | use std::fmt::{Display, Formatter, Result};
12 | use std::convert::From;
13 |
14 | #[derive(Copy, Clone, PartialEq, Eq)]
15 | #[repr(u8)]
16 | pub enum Square {
17 | Empty,
18 | X,
19 | O,
20 | }
21 |
22 | impl Default for Square {
23 | fn default() -> Square {
24 | Square::Empty
25 | }
26 | }
27 |
28 | impl Display for Square {
29 | fn fmt(&self, f: &mut Formatter) -> Result {
30 | write!(f,
31 | "{}",
32 | match *self {
33 | Square::Empty => ' ',
34 | Square::X => 'X',
35 | Square::O => 'O',
36 | })
37 | }
38 | }
39 |
40 | impl From for Square {
41 | fn from(p: minimax::Player) -> Square {
42 | match p {
43 | minimax::Player::Computer => Square::X,
44 | minimax::Player::Opponent => Square::O,
45 | }
46 | }
47 | }
48 |
49 | impl From for minimax::Player {
50 | fn from(s: Square) -> minimax::Player {
51 | match s {
52 | Square::X => minimax::Player::Computer,
53 | Square::O => minimax::Player::Opponent,
54 | _ => panic!("From::from(Square::Empty))"),
55 | }
56 | }
57 | }
58 |
59 | #[derive(Clone, PartialEq, Eq)]
60 | pub struct Board {
61 | squares: [Square; 9],
62 | }
63 |
64 | impl Default for Board {
65 | fn default() -> Board {
66 | Board { squares: [Square::default(); 9] }
67 | }
68 | }
69 |
70 | impl Display for Board {
71 | fn fmt(&self, f: &mut Formatter) -> Result {
72 | try!(writeln!(f,
73 | "{} | {} | {}",
74 | self.squares[0],
75 | self.squares[1],
76 | self.squares[2]));
77 | try!(writeln!(f,
78 | "{} | {} | {}",
79 | self.squares[3],
80 | self.squares[4],
81 | self.squares[5]));
82 | try!(writeln!(f,
83 | "{} | {} | {}",
84 | self.squares[6],
85 | self.squares[7],
86 | self.squares[8]));
87 | Ok(())
88 | }
89 | }
90 |
91 | pub struct Game;
92 |
93 | impl minimax::Game for Game {
94 | type S = Board;
95 | type M = Place;
96 |
97 | fn generate_moves(b: &Board, p: minimax::Player, ms: &mut [Option]) -> usize {
98 | let mut j = 0;
99 | for i in 0..b.squares.len() {
100 | if b.squares[i] == Square::Empty {
101 | ms[j] = Some(Place {
102 | i: i as u8,
103 | s: From::from(p),
104 | });
105 | j += 1;
106 | }
107 | }
108 | ms[j] = None;
109 | j
110 | }
111 |
112 | fn get_winner(b: &Board) -> Option {
113 | // horizontal wins
114 | if b.squares[0] != Square::Empty && b.squares[0] == b.squares[1] &&
115 | b.squares[1] == b.squares[2] {
116 | return Some(minimax::Winner::Competitor(From::from(b.squares[0])));
117 | }
118 | if b.squares[3] != Square::Empty && b.squares[3] == b.squares[4] &&
119 | b.squares[4] == b.squares[5] {
120 | return Some(minimax::Winner::Competitor(From::from(b.squares[3])));
121 | }
122 | if b.squares[6] != Square::Empty && b.squares[6] == b.squares[7] &&
123 | b.squares[7] == b.squares[8] {
124 | return Some(minimax::Winner::Competitor(From::from(b.squares[6])));
125 | }
126 | // vertical wins
127 | if b.squares[0] != Square::Empty && b.squares[0] == b.squares[3] &&
128 | b.squares[3] == b.squares[6] {
129 | return Some(minimax::Winner::Competitor(From::from(b.squares[0])));
130 | }
131 | if b.squares[1] != Square::Empty && b.squares[1] == b.squares[4] &&
132 | b.squares[4] == b.squares[7] {
133 | return Some(minimax::Winner::Competitor(From::from(b.squares[1])));
134 | }
135 | if b.squares[2] != Square::Empty && b.squares[2] == b.squares[5] &&
136 | b.squares[5] == b.squares[8] {
137 | return Some(minimax::Winner::Competitor(From::from(b.squares[2])));
138 | }
139 | // diagonal wins
140 | if b.squares[0] != Square::Empty && b.squares[0] == b.squares[4] &&
141 | b.squares[4] == b.squares[8] {
142 | return Some(minimax::Winner::Competitor(From::from(b.squares[0])));
143 | }
144 | if b.squares[2] != Square::Empty && b.squares[2] == b.squares[4] &&
145 | b.squares[4] == b.squares[6] {
146 | return Some(minimax::Winner::Competitor(From::from(b.squares[2])));
147 | }
148 | // draws
149 | if b.squares.iter().all(|s| *s != Square::Empty) {
150 | Some(minimax::Winner::Draw)
151 | } else {
152 | // non-terminal state
153 | None
154 | }
155 | }
156 | }
157 |
158 | #[derive(Copy, Clone, PartialEq, Eq)]
159 | pub struct Place {
160 | i: u8,
161 | s: Square,
162 | }
163 |
164 | impl Display for Place {
165 | fn fmt(&self, f: &mut Formatter) -> Result {
166 | write!(f, "{}@{}", self.s, self.i)
167 | }
168 | }
169 |
170 | impl minimax::Move for Place {
171 | type G = Game;
172 | fn apply(&self, b: &mut Board) {
173 | b.squares[self.i as usize] = self.s;
174 | }
175 | fn undo(&self, b: &mut Board) {
176 | b.squares[self.i as usize] = Square::Empty;
177 | }
178 | }
179 |
180 | pub struct Evaluator;
181 |
182 | impl minimax::Evaluator for Evaluator {
183 | type G = Game;
184 | // adapted from http://www.cs.olemiss.edu/~dwilkins/CSCI531/tic.c
185 | fn evaluate(b: &Board, mw: Option) -> minimax::Evaluation {
186 | match mw {
187 | Some(minimax::Winner::Competitor(wp)) => match wp {
188 | minimax::Player::Computer => return minimax::Evaluation::Best,
189 | minimax::Player::Opponent => return minimax::Evaluation::Worst,
190 | },
191 | _ => {}
192 | }
193 | let mut score = 0;
194 |
195 | // 3rd: check for doubles
196 | for i in 0..3 {
197 | let line = i * 3;
198 | if b.squares[line + 0] == b.squares[line + 1] {
199 | if b.squares[line + 0] == Square::X {
200 | score += 5;
201 | } else if b.squares[line + 0] == Square::O {
202 | score -= 5;
203 | }
204 | }
205 | if b.squares[line + 1] == b.squares[line + 2] {
206 | if b.squares[line + 1] == Square::X {
207 | score += 5;
208 | } else if b.squares[line + 1] == Square::O {
209 | score += 5;
210 | }
211 | }
212 | if b.squares[i] == b.squares[3 + i] {
213 | if b.squares[i] == Square::X {
214 | score += 5;
215 | } else if b.squares[i] == Square::O {
216 | score -= 5;
217 | }
218 | }
219 | if b.squares[3 + i] == b.squares[6 + i] {
220 | if b.squares[3 + i] == Square::X {
221 | score += 5;
222 | } else if b.squares[3 + i] == Square::O {
223 | score -= 5;
224 | }
225 | }
226 | }
227 | // 2nd: check for the middle square
228 | if b.squares[4] == Square::X {
229 | score += 5;
230 | }
231 | if b.squares[4] == Square::O {
232 | score -= 5;
233 | }
234 | minimax::Evaluation::Score(score)
235 | }
236 | }
237 |
238 | fn main() {
239 | use minimax::{Game, Move, Strategy};
240 | use minimax::strategies::negamax::{Negamax, Options};
241 |
242 | let mut b = Board::default();
243 | let mut strategies = vec![
244 | (minimax::Player::Computer, Negamax::::new(Options { max_depth: 10 })),
245 | (minimax::Player::Opponent, Negamax::::new(Options { max_depth: 10 })),
246 | ];
247 | let mut s = 0;
248 | while self::Game::get_winner(&b).is_none() {
249 | println!("{}", b);
250 | let (p, ref mut strategy) = strategies[s];
251 | match strategy.choose_move(&mut b, p) {
252 | Some(m) => m.apply(&mut b),
253 | None => break,
254 | }
255 | s = 1 - s;
256 | }
257 | println!("{}", b);
258 | }
259 |
--------------------------------------------------------------------------------