tic_tac_toe/
tic_tac_toe.rs

1//
2// TIC TAC TOE
3// Author: John Thingstad
4// Date: February 2022
5//
6
7//------------------------------------------------- UI -------------------------------------
8
9fn to_str(peg: u8) -> String {
10    match peg {
11        0 => String::from(" "),
12        1 => String::from("X"),
13        2 => String::from("O"),
14        _ => panic!("Illegal peg value!"),
15    }
16}
17
18fn print_intro() {
19    println!("TIC TAC TOE\n");
20    println!("Get 3 in a row horizontally, vertically or diagonally to win.\n");
21    println!("Me first!\n");
22}
23
24fn print_board(board: &[[u8; 3]; 3]) {
25    println!("    A   B   C");
26    println!("  +---+---+---+");
27    println!(
28        "1 | {} | {} | {} |",
29        to_str(board[0][0]),
30        to_str(board[0][1]),
31        to_str(board[0][2]),
32    );
33    println!("  +---+---+---+");
34    println!(
35        "2 | {} | {} | {} |",
36        to_str(board[1][0]),
37        to_str(board[1][1]),
38        to_str(board[1][2]),
39    );
40    println!("  +---+---+---+");
41    println!(
42        "3 | {} | {} | {} |",
43        to_str(board[2][0]),
44        to_str(board[2][1]),
45        to_str(board[2][2]),
46    );
47    println!("  +---+---+---+");
48}
49
50fn query_move() -> [usize; 2] {
51    let mut posvec = [0usize; 2];
52    let mut tries = 0;
53    while tries < 5 {
54        tries += 1;
55
56        println!("\nWhat is your move? ");
57        let mut move_str = String::new();
58        let len = std::io::stdin()
59            .read_line(&mut move_str)
60            .expect("read_line failed!");
61        if len != 3 {
62            println!("?");
63            continue;
64        }
65        move_str = move_str.to_uppercase();
66
67        let chars: Vec<&str> = move_str.split("").collect::<Vec<&str>>(); // looks like [ "", "A", "1", ""]
68        match chars[1] {
69            "A" => {
70                posvec[0] = 0;
71            }
72            "B" => {
73                posvec[0] = 1;
74            }
75            "C" => {
76                posvec[0] = 2;
77            }
78            _ => {
79                println!("?");
80                continue;
81            }
82        }
83        match chars[2] {
84            "1" => {
85                posvec[1] = 0;
86            }
87            "2" => {
88                posvec[1] = 1;
89            }
90            "3" => {
91                posvec[1] = 2;
92            }
93            _ => {
94                println!("?");
95                continue;
96            }
97        }
98        break;
99    }
100
101    if tries == 5 {
102        panic!("No answer given.")
103    }
104
105    posvec
106}
107
108fn posvec_to_move(posvec: &[usize; 2]) -> String {
109    let mut posstring = String::new();
110    match posvec[0] {
111        0 => {
112            posstring.push_str("A");
113        }
114        1 => {
115            posstring.push_str("B");
116        }
117        2 => {
118            posstring.push_str("C");
119        }
120        _ => {
121            panic!("Illegal value!");
122        }
123    }
124    match posvec[1] {
125        0 => {
126            posstring.push_str("1");
127        }
128        1 => {
129            posstring.push_str("2");
130        }
131        2 => {
132            posstring.push_str("3");
133        }
134        _ => {
135            panic!("Illegal value!");
136        }
137    }
138    posstring
139}
140
141//--------------------------------- computer moves ------------------------------------
142
143fn decode_candidate(triplet: &[[usize; 2]; 3], the_board: &[[u8; 3]; 3]) -> [u8; 3] {
144    // takes a triplet.
145    // Which is a diagonal, bidigonal, row or col and reads the peg value at that position
146    // returns the three values
147    let mut candidate = [0u8; 3];
148    let mut i = 0;
149    for pos in triplet.iter() {
150        candidate[i] = the_board[pos[1]][pos[0]];
151        i += 1;
152    }
153    return candidate;
154}
155
156fn decode_result(
157    found_match: &[u8; 3],
158    triplet: &[[usize; 2]; 3],
159    the_board: &[[u8; 3]; 3],
160) -> [usize; 2] {
161    // you have three peg's in a row in a diagonal, bidiagonal, row or column, called the triplet
162    // your solution returns a array of three peg's
163    // find the position for the peg that is different
164    for i in 0..3 {
165        let peg = the_board[triplet[i][1]][triplet[i][0]];
166        if peg == found_match[i] {
167            continue;
168        }
169        return triplet[i];
170    }
171    panic!("found_match: not legal values");
172}
173
174fn check_candidate(candidate: [u8; 3]) -> Option<([u8; 3], u8)> {
175    // first value of a state row is the current peg values
176    // the second is the move to take if you have a match
177    // This is irrecspective of wether it is on a diagonal, bidiagonal, row or column
178    // They are ordered from best move to worst
179    // The priority - if several moves match use the one with the highest priority
180    // (which has the lowest value)
181    let state = [
182        [[1, 1, 0], [1, 1, 1]],
183        [[1, 0, 1], [1, 1, 1]],
184        [[0, 1, 1], [1, 1, 1]],
185        [[2, 2, 0], [2, 2, 1]],
186        [[2, 0, 2], [2, 1, 2]],
187        [[0, 2, 2], [1, 2, 2]],
188        [[1, 0, 0], [1, 1, 0]],
189        [[0, 1, 0], [0, 1, 1]],
190        [[0, 0, 1], [1, 0, 1]],
191        [[2, 0, 0], [2, 1, 0]],
192        [[0, 2, 0], [1, 2, 0]],
193        [[0, 0, 2], [0, 1, 2]],
194        [[0, 0, 0], [0, 1, 0]],
195    ];
196    let priority = [1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3];
197
198    let mut i = 0;
199    for [pos, mov] in state {
200        if pos == candidate {
201            return Some((mov, priority[i]));
202        }
203        i += 1;
204    }
205    return None;
206}
207
208fn find_empty_pos(the_board: &[[u8; 3]; 3]) -> [usize; 2] {
209    for j in 0..3 {
210        for i in 0..3 {
211            if the_board[i][j] == 0 {
212                return [j, i];
213            }
214        }
215    }
216    panic!("No empty positions on the board!")
217}
218
219fn make_move(the_board: &[[u8; 3]; 3]) -> [usize; 2] {
220    let triplet_list = [
221        // list of triplets of position on the board
222        [[0, 0], [1, 1], [2, 2]], // diagonal
223        [[0, 2], [1, 1], [2, 0]], // bidiagonal
224        [[0, 0], [0, 1], [0, 2]], // coloms
225        [[1, 0], [1, 1], [1, 2]],
226        [[2, 0], [2, 1], [2, 2]],
227        [[0, 0], [1, 0], [2, 0]], // rows
228        [[0, 1], [1, 1], [2, 1]],
229        [[0, 2], [1, 2], [2, 2]],
230    ];
231    let mut do_move = [1, 1];
232    let mut do_priority = 4;
233    for triplet in triplet_list.iter() {
234        let candidate = decode_candidate(&triplet, &the_board);
235
236        match check_candidate(candidate) {
237            None => continue,
238            Some((found_match, priority)) => {
239                let candidate_move = decode_result(&found_match, &triplet, &the_board);
240                if priority < do_priority {
241                    do_priority = priority;
242                    do_move = candidate_move;
243                }
244            }
245        }
246    }
247    if do_priority == 4 {
248        do_move = find_empty_pos(&the_board);
249    }
250    return do_move;
251}
252
253//--------------------------------- support functions ------------------------------------------------
254
255fn is_empty(your_move: &[usize; 2], the_board: &[[u8; 3]; 3]) -> bool {
256    return the_board[your_move[1]][your_move[0]] == 0;
257}
258
259fn game_won(the_board: &[[u8; 3]; 3]) -> bool {
260    let triplet_list = [
261        // list of triplets of position on the board
262        [[0, 0], [1, 1], [2, 2]], // diagonal
263        [[0, 2], [1, 1], [2, 0]], // bidiagonal
264        [[0, 0], [0, 1], [0, 2]], // coloms
265        [[1, 0], [1, 1], [1, 2]],
266        [[2, 0], [2, 1], [2, 2]],
267        [[0, 0], [1, 0], [2, 0]], // rows
268        [[0, 1], [1, 1], [2, 1]],
269        [[0, 2], [1, 2], [2, 2]],
270    ];
271    'triplet_loop: for triplet in triplet_list.iter() {
272        let peg = the_board[triplet[0][0]][triplet[0][1]];
273        // if board position is blank no three in a row possible
274        if peg == 0 {
275            continue 'triplet_loop;
276        }
277        // skip if not three in a row. The same 'X' or 'O' (called peg) as the first
278        for pos in triplet.iter() {
279            if the_board[pos[0]][pos[1]] != peg {
280                continue 'triplet_loop;
281            }
282        }
283        return true;
284    }
285    return false;
286}
287
288fn board_full(the_board: &[[u8; 3]; 3]) -> bool {
289    let mut empty_count = 0;
290
291    // board is full is the same as saying it has no empty spaces
292    for row in 0..3 {
293        for col in 0..3 {
294            if the_board[row][col] == 0 {
295                empty_count += 1;
296            }
297        }
298    }
299
300    return empty_count == 0;
301}
302
303//-------------------------------------------- main game loop ----------------------------------
304
305/// Plays the child game of Tic, Tac, Toe.
306///
307/// Objective on a 3 x 3 board get thee pegs in a row.
308///
309/// Computer starts the game.
310
311pub fn play() {
312    let mut board = [[0u8; 3]; 3];
313
314    print_intro();
315
316    'game_loop: loop {
317        let my_move = make_move(&board);
318        board[my_move[1]][my_move[0]] = 1;
319        print_board(&board);
320        println!("\nMy move was {}", posvec_to_move(&my_move));
321        if game_won(&board) {
322            print!("\nI won!");
323            break 'game_loop;
324        }
325
326        if board_full(&board) {
327            print!("\nIt's a tie.");
328            break 'game_loop;
329        }
330
331        'query_loop: loop {
332            let your_move = query_move();
333            if !is_empty(&your_move, &board) {
334                println!("{} is occupied!", posvec_to_move(&your_move));
335                continue 'query_loop;
336            }
337
338            board[your_move[1]][your_move[0]] = 2;
339            print_board(&board);
340            if game_won(&board) {
341                println!("\nYou won!");
342                break 'game_loop;
343            }
344            break 'query_loop;
345        }
346        println!("\n-------------------\n")
347    }
348    println!("\nGAME OVER\n");
349}