|
| 1 | +#include <iostream> |
| 2 | +#include <string> |
| 3 | + |
| 4 | +#include <stdlib.h> |
| 5 | + |
| 6 | +const char * CLS = "CLS", |
| 7 | +*TOP = "top row", |
| 8 | +*MIDDLE = "middle", |
| 9 | +*BOTTOM = "bottom row", |
| 10 | +*DIAGONAL = "diagonal", |
| 11 | +*LEFT = "left column", |
| 12 | +*RIGHT = "right column"; |
| 13 | + |
| 14 | +const char PLAYER_X = 'X', |
| 15 | +PLAYER_O = 'O', |
| 16 | +BOARD_DIV = '|', |
| 17 | +BOARD_LINE = '-', |
| 18 | +BOARD_INTERSECTION = '+'; |
| 19 | + |
| 20 | +const char INPUT_PLACEHOLDERS[9] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; |
| 21 | + |
| 22 | +const int MAX_ATTEMPTS = 9, |
| 23 | +BOARD_WIDTH = 3; |
| 24 | + |
| 25 | +typedef struct { unsigned int x, y; } board_pos; |
| 26 | + |
| 27 | +board_pos * get_board_pos_by_input(int & index) { |
| 28 | + auto * pos = new board_pos(); |
| 29 | + pos->x = index % BOARD_WIDTH; |
| 30 | + pos->y = (index - pos->x) / BOARD_WIDTH; |
| 31 | + return pos; |
| 32 | +} |
| 33 | + |
| 34 | +void relative_board_cell( |
| 35 | + std::string & output, char & placeholder, |
| 36 | + const bool & left_corner, const bool & right_corner |
| 37 | +) { |
| 38 | + if (left_corner) |
| 39 | + output += " "; |
| 40 | + |
| 41 | + if (!left_corner && !right_corner) |
| 42 | + output += BOARD_DIV; |
| 43 | + |
| 44 | + output += " "; |
| 45 | + |
| 46 | + if (placeholder == PLAYER_X || placeholder == PLAYER_O) |
| 47 | + output += placeholder; |
| 48 | + else |
| 49 | + output += std::to_string((int)placeholder); |
| 50 | + |
| 51 | + output += " "; |
| 52 | + |
| 53 | + if (right_corner) |
| 54 | + output += "\n"; |
| 55 | + |
| 56 | + if (!left_corner && !right_corner) |
| 57 | + output += BOARD_DIV; |
| 58 | +} |
| 59 | + |
| 60 | +void board_row_separator(std::string & output) { |
| 61 | + output += " "; |
| 62 | + output.append(3, BOARD_LINE); |
| 63 | + output += BOARD_INTERSECTION; |
| 64 | + output.append(3, BOARD_LINE); |
| 65 | + output += BOARD_INTERSECTION; |
| 66 | + output.append(3, BOARD_LINE); |
| 67 | + output += "\n"; |
| 68 | +} |
| 69 | + |
| 70 | +bool board_check_horizontal( |
| 71 | + char(&board_inputs)[BOARD_WIDTH][BOARD_WIDTH], |
| 72 | + const int & row, const char & player |
| 73 | +) { |
| 74 | + return board_inputs[row][0] == player |
| 75 | + && board_inputs[row][1] == player |
| 76 | + && board_inputs[row][2] == player; |
| 77 | +} |
| 78 | + |
| 79 | +bool board_check_vertical( |
| 80 | + char(&board_inputs)[BOARD_WIDTH][BOARD_WIDTH], |
| 81 | + const int & col, const char & player |
| 82 | +) { |
| 83 | + return board_inputs[0][col] == player |
| 84 | + && board_inputs[1][col] == player |
| 85 | + && board_inputs[2][col] == player; |
| 86 | +} |
| 87 | + |
| 88 | +bool board_check_diagonal( |
| 89 | + char(&board_inputs)[BOARD_WIDTH][BOARD_WIDTH], |
| 90 | + const char & player |
| 91 | +) { |
| 92 | + return (board_inputs[0][0] == player |
| 93 | + && board_inputs[1][1] == player |
| 94 | + && board_inputs[2][2] == player) |
| 95 | + || (board_inputs[0][2] == player |
| 96 | + && board_inputs[1][1] == player |
| 97 | + && board_inputs[2][0] == player); |
| 98 | +} |
| 99 | + |
| 100 | +void board_check_player( |
| 101 | + char(&board_inputs)[BOARD_WIDTH][BOARD_WIDTH], |
| 102 | + char & winner, const char * & condition, |
| 103 | + const char & player, std::string & condition_fmt |
| 104 | +) { |
| 105 | + if (board_check_horizontal(board_inputs, 0, player)) { |
| 106 | + condition = TOP; |
| 107 | + winner = player; |
| 108 | + return; |
| 109 | + } |
| 110 | + |
| 111 | + if (board_check_horizontal(board_inputs, 1, player)) { |
| 112 | + condition_fmt += MIDDLE; |
| 113 | + condition_fmt += " row"; |
| 114 | + condition = condition_fmt.c_str(); |
| 115 | + winner = player; |
| 116 | + return; |
| 117 | + } |
| 118 | + |
| 119 | + if (board_check_horizontal(board_inputs, 2, player)) { |
| 120 | + condition = BOTTOM; |
| 121 | + winner = player; |
| 122 | + return; |
| 123 | + } |
| 124 | + |
| 125 | + if (board_check_vertical(board_inputs, 0, player)) { |
| 126 | + condition = LEFT; |
| 127 | + winner = player; |
| 128 | + return; |
| 129 | + } |
| 130 | + |
| 131 | + if (board_check_vertical(board_inputs, 1, player)) { |
| 132 | + condition_fmt += MIDDLE; |
| 133 | + condition_fmt += " column"; |
| 134 | + condition = condition_fmt.c_str(); |
| 135 | + winner = player; |
| 136 | + return; |
| 137 | + } |
| 138 | + |
| 139 | + if (board_check_vertical(board_inputs, 2, player)) { |
| 140 | + condition = RIGHT; |
| 141 | + winner = player; |
| 142 | + return; |
| 143 | + } |
| 144 | + |
| 145 | + if (board_check_diagonal(board_inputs, player)) { |
| 146 | + condition = DIAGONAL; |
| 147 | + winner = player; |
| 148 | + return; |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +void get_player_choice( |
| 153 | + int & choice, int & index, char(&board_inputs)[BOARD_WIDTH][BOARD_WIDTH], |
| 154 | + bool & is_occupied, char & current_player, char & last_player, int & attempts |
| 155 | +) { |
| 156 | + if (choice != 0 && choice != -1) { |
| 157 | + index = choice - 1; |
| 158 | + |
| 159 | + auto * pos = get_board_pos_by_input(index); |
| 160 | + auto * cell = &board_inputs[pos->y][pos->x]; |
| 161 | + |
| 162 | + is_occupied = *cell == PLAYER_X || *cell == PLAYER_O; |
| 163 | + if (!is_occupied) { |
| 164 | + *cell = current_player; |
| 165 | + last_player = current_player; |
| 166 | + current_player = current_player == PLAYER_X ? PLAYER_O : PLAYER_X; |
| 167 | + attempts++; |
| 168 | + } |
| 169 | + } |
| 170 | +} |
| 171 | + |
| 172 | +bool check_for_end_game(int & attempts, char & winner) { |
| 173 | + return winner != ' ' || attempts == MAX_ATTEMPTS; |
| 174 | +} |
| 175 | + |
| 176 | +void display_board(void) { |
| 177 | + auto last_player = PLAYER_O, |
| 178 | + current_player = PLAYER_X, |
| 179 | + winner = ' '; |
| 180 | + |
| 181 | + auto attempts = 0, |
| 182 | + choice = -1, |
| 183 | + index = 0; |
| 184 | + |
| 185 | + auto is_occupied = false, is_game_over = false; |
| 186 | + char board_inputs[BOARD_WIDTH][BOARD_WIDTH] = {}; |
| 187 | + const char * condition; |
| 188 | + std::string output, condition_fmt; |
| 189 | + |
| 190 | + for (auto row = 0; row < BOARD_WIDTH; row++) |
| 191 | + for (auto col = 0; col < BOARD_WIDTH; col++) |
| 192 | + board_inputs[row][col] = 1 + col + row * BOARD_WIDTH; |
| 193 | + |
| 194 | + condition_fmt.clear(); |
| 195 | + |
| 196 | + while (true) { |
| 197 | + output.clear(); |
| 198 | + output += "Current board state:\n"; |
| 199 | + |
| 200 | + get_player_choice( |
| 201 | + choice, index, board_inputs, is_occupied, |
| 202 | + current_player, last_player, attempts |
| 203 | + ); |
| 204 | + |
| 205 | + for (index = 0; index < BOARD_WIDTH; index++) |
| 206 | + { |
| 207 | + // Board formatting pattern: |
| 208 | + // 1 | 2 | 3 |
| 209 | + // ---+---+--- |
| 210 | + // 4 | 5 | 6 |
| 211 | + // ---+---+--- |
| 212 | + // 7 | 8 | 9 |
| 213 | + |
| 214 | + auto is_mid_row = index == 1; |
| 215 | + if (is_mid_row) |
| 216 | + board_row_separator(output); |
| 217 | + |
| 218 | + relative_board_cell(output, board_inputs[index][0], true, false); |
| 219 | + relative_board_cell(output, board_inputs[index][1], false, false); |
| 220 | + relative_board_cell(output, board_inputs[index][2], false, true); |
| 221 | + |
| 222 | + if (is_mid_row) |
| 223 | + board_row_separator(output); |
| 224 | + } |
| 225 | + |
| 226 | + if (choice == 0) |
| 227 | + output += "\nNot a valid choice. Try again.\n"; |
| 228 | + |
| 229 | + if (is_occupied) { |
| 230 | + output += "\nThat square is not available. Try again.\n"; |
| 231 | + is_occupied = false; |
| 232 | + } |
| 233 | + |
| 234 | + board_check_player(board_inputs, winner, condition, PLAYER_X, condition_fmt); |
| 235 | + |
| 236 | + if (winner != PLAYER_X) |
| 237 | + board_check_player(board_inputs, winner, condition, PLAYER_O, condition_fmt); |
| 238 | + |
| 239 | + if (check_for_end_game(attempts, winner)) |
| 240 | + break; |
| 241 | + |
| 242 | + output += "\nPlayer "; |
| 243 | + output += current_player; |
| 244 | + output += ", enter a number between 1 and 9: "; |
| 245 | + std::cout << output << std::flush; |
| 246 | + std::cin >> choice; |
| 247 | + std::system(CLS); |
| 248 | + |
| 249 | + if (choice < 1 || choice > 9) |
| 250 | + choice = 0; |
| 251 | + } |
| 252 | + |
| 253 | + if (winner != ' ') { |
| 254 | + output += "\nPlayer "; |
| 255 | + output += winner; |
| 256 | + output += " wins on the "; |
| 257 | + output += condition; |
| 258 | + output += "!"; |
| 259 | + } |
| 260 | + else |
| 261 | + output += "\nDraw. Nobody wins."; |
| 262 | + |
| 263 | + std::cout << output << std::endl << std::endl; |
| 264 | +} |
| 265 | + |
| 266 | +int main(void) |
| 267 | +{ |
| 268 | + /* |
| 269 | + [Module 8] - Application Structure: |
| 270 | + This is a refactored version of TicTacToe.cpp from module 6. |
| 271 | +
|
| 272 | + Contains code organisation, virtualization and more changes |
| 273 | + for ease logic and code concept understanding. |
| 274 | + */ |
| 275 | + display_board(); |
| 276 | + |
| 277 | + return EXIT_SUCCESS; |
| 278 | +} |
0 commit comments