tic_tac_rust/
lib.rs

1use rand::{thread_rng, Rng};
2use wasm_bindgen::prelude::*;
3
4#[wasm_bindgen]
5#[repr(u8)]
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum Difficulty {
8    Easy = 0,
9    Medium = 1,
10    Hard = 2,
11}
12
13#[wasm_bindgen]
14pub struct State {
15    board: Vec<Vec<char>>,
16    difficulty: Difficulty,
17}
18
19#[wasm_bindgen]
20pub struct Move {
21    pub index: usize,
22    pub score: i32,
23}
24
25#[wasm_bindgen]
26impl State {
27    pub fn new(diff: Difficulty) -> State {
28        State {
29            board: vec![vec!['0'; 3]; 3],
30            difficulty: diff,
31        }
32    }
33
34    fn get_board(&self) -> Vec<Vec<char>> {
35        self.board.clone()
36    }
37
38    pub fn update_board(&mut self, index: usize, val: char) {
39        if index <= 8 && (val == 'x' || val == 'o' || val == '0') {
40            let value_orig = self.get_val_by_index(index);
41            if (value_orig != 'x' && value_orig != 'o') || val == '0' {
42                self.set_val_by_index(index, val);
43            }
44        }
45    }
46    pub fn get_val_by_index(&self, index: usize) -> char {
47        if index < 3 {
48            self.board[0][index]
49        } else if index < 6 {
50            self.board[1][index - 3]
51        } else if index < 9 {
52            self.board[2][index - 6]
53        } else {
54            '0'
55        }
56    }
57    fn set_val_by_index(&mut self, index: usize, val: char) {
58        if index < 3 {
59            let mut board = self.get_board();
60            let mut inner = board[0].clone();
61            inner[index] = val;
62            board[0] = inner;
63            self.set_board(board);
64        } else if index < 6 {
65            let mut board = self.get_board();
66            let mut inner = board[1].clone();
67            inner[index - 3] = val;
68            board[1] = inner;
69            self.set_board(board);
70        } else {
71            let mut board = self.get_board();
72            let mut inner = board[2].clone();
73            inner[index - 6] = val;
74            board[2] = inner;
75            self.set_board(board);
76        }
77    }
78    fn set_board(&mut self, new_board: Vec<Vec<char>>) -> () {
79        self.board = new_board;
80    }
81
82    pub fn is_tie(&self) -> bool {
83        self.get_empty_spots().is_empty()
84    }
85
86    pub fn is_win(&self, x_or_o: char) -> bool {
87        (self.board[0][0] == x_or_o && self.board[0][1] == x_or_o && self.board[0][2] == x_or_o)
88            || (self.board[1][0] == x_or_o
89                && self.board[1][1] == x_or_o
90                && self.board[1][2] == x_or_o)
91            || (self.board[2][0] == x_or_o
92                && self.board[2][1] == x_or_o
93                && self.board[2][2] == x_or_o)
94            || (self.board[0][0] == x_or_o
95                && self.board[1][0] == x_or_o
96                && self.board[2][0] == x_or_o)
97            || (self.board[0][1] == x_or_o
98                && self.board[1][1] == x_or_o
99                && self.board[2][1] == x_or_o)
100            || (self.board[0][2] == x_or_o
101                && self.board[1][2] == x_or_o
102                && self.board[2][2] == x_or_o)
103            || (self.board[0][0] == x_or_o
104                && self.board[1][1] == x_or_o
105                && self.board[2][2] == x_or_o)
106            || (self.board[0][2] == x_or_o
107                && self.board[1][1] == x_or_o
108                && self.board[2][0] == x_or_o)
109    }
110
111    pub fn reset_board(&mut self, diff: Difficulty) {
112        self.board = vec![vec!['0'; 3]; 3];
113        self.difficulty = diff;
114    }
115
116    pub fn next_move(&mut self, is_x: bool) -> usize {
117        match self.difficulty {
118            Difficulty::Easy => {
119                if rand::random() {
120                    self.random_next_move(is_x).index
121                } else {
122                    self.best_next_move(is_x).index
123                }
124            }
125            Difficulty::Medium => {
126                let mut rng = thread_rng();
127                let value: f64 = rng.gen();
128                if value > 0.8 {
129                    self.random_next_move(is_x).index
130                } else {
131                    self.best_next_move(is_x).index
132                }
133            }
134            Difficulty::Hard => self.best_next_move(is_x).index,
135        }
136    }
137    fn random_next_move(&self, _is_x: bool) -> Move {
138        let empties = self.get_empty_spots();
139        let random_index: usize = thread_rng().gen_range(0, empties.len());
140        Move {
141            score: 0,
142            index: empties[random_index],
143        }
144    }
145    pub fn best_next_move(&mut self, is_x: bool) -> Move {
146        let player_1 = 'x';
147        let player_2 = 'o';
148        if self.is_win(player_1) {
149            return Move {
150                index: 0,
151                score: 10,
152            };
153        }
154        if self.is_win(player_2) {
155            return Move {
156                index: 0,
157                score: -10,
158            };
159        }
160
161        let empty_spots = self.get_empty_spots();
162        if empty_spots.is_empty() {
163            return Move { index: 0, score: 0 };
164        }
165        let mut moves = Vec::new();
166        for index in empty_spots {
167            let curr_player = if is_x { 'x' } else { 'o' };
168
169            self.update_board(index, curr_player);
170            let curr_move = self.best_next_move(!is_x);
171            moves.push(Move {
172                score: curr_move.score,
173                index,
174            });
175            self.update_board(index, '0');
176        }
177        if is_x {
178            let mut best_score: i32 = -10_000_000;
179            let mut best_score_index: usize = 0;
180            for value in moves {
181                if value.score > best_score {
182                    best_score = value.score;
183                    best_score_index = value.index;
184                }
185            }
186            Move {
187                index: best_score_index,
188                score: best_score,
189            }
190        } else {
191            let mut best_score: i32 = 10_000_000;
192            let mut best_score_index: usize = 0;
193            for value in moves {
194                if value.score < best_score {
195                    best_score = value.score;
196                    best_score_index = value.index;
197                }
198            }
199
200            Move {
201                index: best_score_index,
202                score: best_score,
203            }
204        }
205    }
206    fn get_empty_spots(&self) -> Vec<usize> {
207        let mut result = Vec::new();
208        for (index_i, row) in self.get_board().iter().enumerate() {
209            for (index_j, value) in row.iter().enumerate() {
210                if *value == '0' {
211                    if index_i == 0 {
212                        result.push(index_j);
213                    } else if index_i == 1 {
214                        result.push(index_j + 3);
215                    } else {
216                        result.push(index_j + 6);
217                    }
218                }
219            }
220        }
221        result
222    }
223}