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}