深度优先Tic Tac Toe

  1. #[allow(unused_imports)]
  2. use std::io;
  3. //Interactive Tic-Tac-Toe play needs the "rand = "0.8.3" crate.
  4. //#[cfg(not(test))]
  5. //extern crate rand;
  6. //#[cfg(not(test))]
  7. //use rand::Rng;
  8. #[derive(Copy, Clone, PartialEq, Debug)]
  9. struct Position {
  10. x: u8,
  11. y: u8,
  12. }
  13. #[derive(Copy, Clone, PartialEq, Debug)]
  14. pub enum Players {
  15. Blank,
  16. PlayerX,
  17. PlayerO,
  18. }
  19. #[derive(Copy, Clone, PartialEq, Debug)]
  20. struct SinglePlayAction {
  21. position: Position,
  22. side: Players,
  23. }
  24. #[derive(Clone, PartialEq, Debug)]
  25. pub struct PlayActions {
  26. positions: Vec<Position>,
  27. side: Players,
  28. }
  29. #[allow(dead_code)]
  30. #[cfg(not(test))]
  31. fn main() {
  32. let mut board = vec![vec![Players::Blank; 3]; 3];
  33. while !available_positions(&board).is_empty()
  34. && !win_check(Players::PlayerX, &board)
  35. && !win_check(Players::PlayerO, &board)
  36. {
  37. display_board(&board);
  38. println!("Type in coordinate for X mark to be played. ie. a1 etc.");
  39. let mut input = String::new();
  40. io::stdin()
  41. .read_line(&mut input)
  42. .expect("Failed to read line");
  43. let mut move_position: Option<Position> = None;
  44. input.make_ascii_lowercase();
  45. let bytes = input.trim().trim_start().as_bytes();
  46. if bytes.len() as u32 == 2
  47. && (bytes[0] as char).is_alphabetic()
  48. && (bytes[1] as char).is_numeric()
  49. {
  50. let column: u8 = bytes[0] - b'a';
  51. let row: u8 = bytes[1] - b'1';
  52. if column <= 2 && row <= 2 {
  53. move_position = Some(Position { x: column, y: row });
  54. }
  55. }
  56. //Take the validated user input coordinate and use it.
  57. if let Some(move_pos) = move_position {
  58. let open_positions = available_positions(&board);
  59. let mut search = open_positions.iter();
  60. let result = search.find(|&&x| x == move_pos);
  61. if result.is_none() {
  62. println!("Not a valid empty coordinate.");
  63. continue;
  64. } else {
  65. board[move_pos.y as usize][move_pos.x as usize] = Players::PlayerX;
  66. if win_check(Players::PlayerX, &board) {
  67. display_board(&board);
  68. println!("Player X Wins!");
  69. return;
  70. }
  71. }
  72. //Find the best game plays from the current board state
  73. let recusion_result = minimax(Players::PlayerO, &board);
  74. match recusion_result {
  75. Some(x) => {
  76. //Interactive Tic-Tac-Toe play needs the "rand = "0.8.3" crate.
  77. //#[cfg(not(test))]
  78. //let random_selection = rand::thread_rng().gen_range(0..x.positions.len());
  79. let random_selection = 0;
  80. let response_pos = x.positions[random_selection];
  81. board[response_pos.y as usize][response_pos.x as usize] = Players::PlayerO;
  82. if win_check(Players::PlayerO, &board) {
  83. display_board(&board);
  84. println!("Player O Wins!");
  85. return;
  86. }
  87. }
  88. None => {
  89. display_board(&board);
  90. println!("Draw game.");
  91. return;
  92. }
  93. }
  94. }
  95. }
  96. }
  97. #[allow(dead_code)]
  98. fn display_board(board: &[Vec<Players>]) {
  99. println!();
  100. for (y, board_row) in board.iter().enumerate() {
  101. print!("{} ", (y + 1));
  102. for board_cell in board_row {
  103. match board_cell {
  104. Players::PlayerX => print!("X "),
  105. Players::PlayerO => print!("O "),
  106. Players::Blank => print!("_ "),
  107. }
  108. }
  109. println!();
  110. }
  111. println!(" a b c");
  112. }
  113. fn available_positions(board: &[Vec<Players>]) -> Vec<Position> {
  114. let mut available: Vec<Position> = Vec::new();
  115. for (y, board_row) in board.iter().enumerate() {
  116. for (x, board_cell) in board_row.iter().enumerate() {
  117. if *board_cell == Players::Blank {
  118. available.push(Position {
  119. x: x as u8,
  120. y: y as u8,
  121. });
  122. }
  123. }
  124. }
  125. available
  126. }
  127. fn win_check(player: Players, board: &[Vec<Players>]) -> bool {
  128. if player == Players::Blank {
  129. return false;
  130. }
  131. //Check for a win on the diagonals.
  132. if (board[0][0] == board[1][1]) && (board[1][1] == board[2][2]) && (board[2][2] == player)
  133. || (board[2][0] == board[1][1]) && (board[1][1] == board[0][2]) && (board[0][2] == player)
  134. {
  135. return true;
  136. }
  137. for i in 0..3 {
  138. //Check for a win on the horizontals.
  139. if (board[i][0] == board[i][1]) && (board[i][1] == board[i][2]) && (board[i][2] == player) {
  140. return true;
  141. }
  142. //Check for a win on the verticals.
  143. if (board[0][i] == board[1][i]) && (board[1][i] == board[2][i]) && (board[2][i] == player) {
  144. return true;
  145. }
  146. }
  147. false
  148. }
  149. //Minimize the actions of the opponent while maximizing the game state of the current player.
  150. pub fn minimax(side: Players, board: &[Vec<Players>]) -> Option<PlayActions> {
  151. //Check that board is in a valid state.
  152. if win_check(Players::PlayerX, board) || win_check(Players::PlayerO, board) {
  153. return None;
  154. }
  155. let opposite = match side {
  156. Players::PlayerX => Players::PlayerO,
  157. Players::PlayerO => Players::PlayerX,
  158. Players::Blank => panic!("Minimax can't operate when a player isn't specified."),
  159. };
  160. let positions = available_positions(board);
  161. if positions.is_empty() {
  162. return None;
  163. }
  164. //Play position
  165. let mut best_move: Option<PlayActions> = None;
  166. for pos in positions {
  167. let mut board_next = board.to_owned();
  168. board_next[pos.y as usize][pos.x as usize] = side;
  169. //Check for a win condition before recursion to determine if this node is terminal.
  170. if win_check(Players::PlayerX, &board_next) {
  171. append_playaction(
  172. side,
  173. &mut best_move,
  174. SinglePlayAction {
  175. position: pos,
  176. side: Players::PlayerX,
  177. },
  178. );
  179. continue;
  180. }
  181. if win_check(Players::PlayerO, &board_next) {
  182. append_playaction(
  183. side,
  184. &mut best_move,
  185. SinglePlayAction {
  186. position: pos,
  187. side: Players::PlayerO,
  188. },
  189. );
  190. continue;
  191. }
  192. let result = minimax(opposite, &board_next);
  193. let current_score = match result {
  194. Some(x) => x.side,
  195. _ => Players::Blank,
  196. };
  197. append_playaction(
  198. side,
  199. &mut best_move,
  200. SinglePlayAction {
  201. position: pos,
  202. side: current_score,
  203. },
  204. )
  205. }
  206. best_move
  207. }
  208. //Promote only better or collate equally scored game plays
  209. fn append_playaction(
  210. current_side: Players,
  211. opt_play_actions: &mut Option<PlayActions>,
  212. appendee: SinglePlayAction,
  213. ) {
  214. if opt_play_actions.is_none() {
  215. *opt_play_actions = Some(PlayActions {
  216. positions: vec![appendee.position],
  217. side: appendee.side,
  218. });
  219. return;
  220. }
  221. let mut play_actions = opt_play_actions.as_mut().unwrap();
  222. //New game action is scored from the current side and the current saved best score against the new game action.
  223. match (current_side, play_actions.side, appendee.side) {
  224. (Players::Blank, _, _) => panic!("Unreachable state."),
  225. //Winning scores
  226. (Players::PlayerX, Players::PlayerX, Players::PlayerX) => {
  227. play_actions.positions.push(appendee.position);
  228. }
  229. (Players::PlayerX, Players::PlayerX, _) => {}
  230. (Players::PlayerO, Players::PlayerO, Players::PlayerO) => {
  231. play_actions.positions.push(appendee.position);
  232. }
  233. (Players::PlayerO, Players::PlayerO, _) => {}
  234. //Non-winning to Winning scores
  235. (Players::PlayerX, _, Players::PlayerX) => {
  236. play_actions.side = Players::PlayerX;
  237. play_actions.positions.clear();
  238. play_actions.positions.push(appendee.position);
  239. }
  240. (Players::PlayerO, _, Players::PlayerO) => {
  241. play_actions.side = Players::PlayerO;
  242. play_actions.positions.clear();
  243. play_actions.positions.push(appendee.position);
  244. }
  245. //Losing to Neutral scores
  246. (Players::PlayerX, Players::PlayerO, Players::Blank) => {
  247. play_actions.side = Players::Blank;
  248. play_actions.positions.clear();
  249. play_actions.positions.push(appendee.position);
  250. }
  251. (Players::PlayerO, Players::PlayerX, Players::Blank) => {
  252. play_actions.side = Players::Blank;
  253. play_actions.positions.clear();
  254. play_actions.positions.push(appendee.position);
  255. }
  256. //Ignoring lower scored plays
  257. (Players::PlayerX, Players::Blank, Players::PlayerO) => {}
  258. (Players::PlayerO, Players::Blank, Players::PlayerX) => {}
  259. //No change hence append only
  260. (_, _, _) => {
  261. assert!(play_actions.side == appendee.side);
  262. play_actions.positions.push(appendee.position);
  263. }
  264. }
  265. }
  266. #[cfg(test)]
  267. mod test {
  268. use super::*;
  269. #[test]
  270. fn win_state_check() {
  271. let mut board = vec![vec![Players::Blank; 3]; 3];
  272. board[0][0] = Players::PlayerX;
  273. board[0][1] = Players::PlayerX;
  274. board[0][2] = Players::PlayerX;
  275. let responses = minimax(Players::PlayerO, &board);
  276. assert_eq!(responses, None);
  277. }
  278. #[test]
  279. fn win_state_check2() {
  280. let mut board = vec![vec![Players::Blank; 3]; 3];
  281. board[0][0] = Players::PlayerX;
  282. board[0][1] = Players::PlayerO;
  283. board[1][0] = Players::PlayerX;
  284. board[1][1] = Players::PlayerO;
  285. board[2][1] = Players::PlayerO;
  286. let responses = minimax(Players::PlayerO, &board);
  287. assert_eq!(responses, None);
  288. }
  289. #[test]
  290. fn block_win_move() {
  291. let mut board = vec![vec![Players::Blank; 3]; 3];
  292. board[0][0] = Players::PlayerX;
  293. board[0][1] = Players::PlayerX;
  294. board[1][2] = Players::PlayerO;
  295. board[2][2] = Players::PlayerO;
  296. let responses = minimax(Players::PlayerX, &board);
  297. assert_eq!(
  298. responses,
  299. Some(PlayActions {
  300. positions: vec![Position { x: 2, y: 0 }],
  301. side: Players::PlayerX
  302. })
  303. );
  304. }
  305. #[test]
  306. fn block_move() {
  307. let mut board = vec![vec![Players::Blank; 3]; 3];
  308. board[0][1] = Players::PlayerX;
  309. board[0][2] = Players::PlayerO;
  310. board[2][0] = Players::PlayerO;
  311. let responses = minimax(Players::PlayerX, &board);
  312. assert_eq!(
  313. responses,
  314. Some(PlayActions {
  315. positions: vec![Position { x: 1, y: 1 }],
  316. side: Players::Blank
  317. })
  318. );
  319. }
  320. #[test]
  321. fn expected_loss() {
  322. let mut board = vec![vec![Players::Blank; 3]; 3];
  323. board[0][0] = Players::PlayerX;
  324. board[0][2] = Players::PlayerO;
  325. board[1][0] = Players::PlayerX;
  326. board[2][0] = Players::PlayerO;
  327. board[2][2] = Players::PlayerO;
  328. let responses = minimax(Players::PlayerX, &board);
  329. assert_eq!(
  330. responses,
  331. Some(PlayActions {
  332. positions: vec![
  333. Position { x: 1, y: 0 },
  334. Position { x: 1, y: 1 },
  335. Position { x: 2, y: 1 },
  336. Position { x: 1, y: 2 }
  337. ],
  338. side: Players::PlayerO
  339. })
  340. );
  341. }
  342. }