Skip to content

Instantly share code, notes, and snippets.

@depperm
Last active February 13, 2024 11:44
Show Gist options
  • Save depperm/b28798d3730c0f8394fd1ed380fa95e5 to your computer and use it in GitHub Desktop.
Save depperm/b28798d3730c0f8394fd1ed380fa95e5 to your computer and use it in GitHub Desktop.
TicTacToe refactor
/*
* tictactoe.c
*
* Copyright 2024 Reslashd https://github.com/Reslashd/tictactoe/blob/main/tictactoe.c
*
* Review: https://codereview.stackexchange.com/posts/289272
*
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
bool checkMove(char grid[][3], int move);
bool checkThree(char grid[][3]);
char checkTwo(char grid[][3]);
void clearScreen(void);
char cpuMove(char grid[][3], int turn, int difficulty);
void drawGrid(char grid[][3]);
int gameLoop(char grid[][3], int numPlayers, int difficulty);
char getMove(char grid[][3], char mark, int numPlayers, int turns, int difficulty);
void makeMove(char grid[][3], int move, char mark);
void pressToContinue(void);
void resetGrid(char grid[][3]);
int setDifficulty(int currentDifficulty);
int showMenu(void);
void showScores(int n_owins, int n_xwins, int n_draws);
char getStartingMark(int numPlayers);
char switchMark(char currentMark);
char randMove(char grid[][3]);
int main(void){
char grid[3][3] = {{'1','2','3'}, {'4','5','6'}, {'7','8','9'}};
int choice = '0';
char difficulty = '1';
int n_draws = 0;
int n_owins = 0;
int n_xwins = 0;
int winner = 0;
srand((unsigned int)time(NULL));
while ((choice = showMenu()) != '5'){
if (choice == '1' || choice == '2'){
winner = gameLoop(grid, choice, difficulty);
clearScreen();
drawGrid(grid);
if(winner == 1){
puts("\nThree in a row!");
puts("O wins!");
n_owins++;
}
else if (winner == 2){
puts("\nThree in a row!");
puts("X wins!");
n_xwins++;
}
else{
puts("\nIts a draw!");
n_draws++;
}
resetGrid(grid);
winner = 0;
pressToContinue();
} else if (choice == '3') {
showScores(n_owins, n_xwins, n_draws);
} else if (choice == '4') {
difficulty = setDifficulty(difficulty);
}
}
return EXIT_SUCCESS;
}
bool checkMove(char grid[][3], int move){
/*
* Check move by checking if 'move' converted to int
* is equal to grid position.
*
* Example check move '2' which is grid position ([0][1])
*
* Row is 50 - 49 = 1 / 3 = 0.33
* Integer division so this is rounded down towards 0.
* Column = 50 - 49 = 1 % 3 = 1
* Result [0][1] which on the grid is '2'.
*
*/
bool move_ok = false;
if (move >= '1' && move <= '9'){
int row = (move - 49) / 3;
int column = (move - 49) % 3;
if(grid[row][column] == move){
move_ok = true;
}
}
return move_ok;
}
char randMove(char grid[][3]){
char randMove;
do{
randMove = (rand() % 9) + 49;
} while(!checkMove(grid, randMove));
return randMove; // Failsafe return random move.
}
bool checkThree(char grid[][3]){
/*
* This functions checks for 'three in a row' by checking the
* if the 3 adjacent grid cells have the same value ('X' or 'O')
* Returns true if a three in a row is found.
*/
// check diagonal left to right
if (grid[0][0] == grid[1][1] && grid[1][1] == grid[2][2]) {
return true;
}
// check diagonal right to left
if (grid[0][2] == grid[1][1] && grid[1][1] == grid[2][0]) {
return true;
}
for(int i=0;i<3;i++){
// row check
if(grid[i][0] == grid[i][1] && grid[i][1] == grid[i][2]){
return true;
}
// col check
if(grid[0][i] == grid[1][i] && grid[1][i] == grid[2][i]){
return true;
}
}
return false;
}
char checkTwo(char grid[][3]){
/*
* This function checks if there are 2 grid cells with the same value
* in a row, column or one of the diagonals and if a counter or
* winning move is possible (there is no X or O in the cell).
* It then returns a counter/winning move on the grid to make i.e. '1'
* to make a move for the top left position on the grid.
*/
char gridPos[3][3] = {{'1','2','3'}, {'4','5','6'}, {'7','8','9'}};
for(int i=0;i<3;i++){
// row check
if(grid[i][0] == grid[i][1] && grid[i][1] != grid[i][2]){
if(checkMove(grid, gridPos[i][2])){
return gridPos[i][2];
}
} else if (grid[i][0] != grid[i][1] && grid[i][1] == grid[i][2]) {
if (checkMove(grid, gridPos[i][0])){
return gridPos[i][0];
}
} else if (grid[i][0] == grid[i][2]) {
if (checkMove(grid, gridPos[i][1])){
return gridPos[i][1];
}
}
// col check
if(grid[0][i] == grid[1][i] && grid[1][i] != grid[2][i]){
if(checkMove(grid, gridPos[2][i])){
return gridPos[2][i];
}
} else if (grid[0][i] != grid[1][i] && grid[1][i] == grid[2][i]) {
if (checkMove(grid, gridPos[0][i])){
return gridPos[0][i];
}
} else if (grid[0][i] == grid[2][i]) {
if (checkMove(grid, gridPos[1][i])){
return gridPos[1][i];
}
}
}
// check diagonal left to right
if (grid[0][0] == grid[1][1] && grid[1][1] != grid[2][2]) {
if (checkMove(grid, '9')){
return '9';
}
} else if (grid[0][0] != grid[1][1] && grid[1][1] == grid[2][2]) {
if (checkMove(grid, '1')){
return '1';
}
} else if (grid[0][0] == grid[2][2]) {
if (checkMove(grid, '5')){
return '5';
}
}
// check diagonal right to left
if (grid[0][2] == grid[1][1] && grid[1][1] != grid[2][0]) {
if (checkMove(grid, '7')){
return '7';
}
} else if (grid[0][2] != grid[1][1] && grid[1][1] == grid[2][0]) {
if (checkMove(grid, '3')){
return '3';
}
} else if (grid[0][2] == grid[2][0]) {
if (checkMove(grid, '5')){
return '5';
}
}
return randMove(grid);
}
void clearScreen(void){
#if defined(__linux__) || defined(__unix__) || defined(__APPLE__)
system("clear");
#endif
#if defined(_WIN32) || defined(_WIN64)
system("cls");
#endif
}
char cpuMove(char grid[][3], int turn, int difficulty){
char first_turn[5] = {'1','3', '5', '7', '9'};
char second_turn[4] = {'1','3', '7', '9'};
int n = 0;
char move = '0';
if(turn == 1){
// First turn strategy - take center or a corner
n = rand() % 5;
move = first_turn[n];
} else if (turn == 2){
// Second turn strategy - take center if possible else a corner
if (checkMove(grid, '5')){
move = '5';
}
else {
n = rand() % 4;
move = second_turn[n];
}
} else if (turn == 3){
// Third turn strategy - take center if possible else random move
if(checkMove(grid, '5')){
move = '5';
}
else {
move = randMove(grid);
}
// Remaining turns
} else {
//easy
if (difficulty == '1'){
move = randMove(grid); // Random move '1' - '9'
}
// hard
else if (difficulty == '2'){
move = checkTwo(grid); // Analyze grid and make move
}
}
return move;
}
void drawGrid(char grid[][3]){
printf("\n%c | %c | %c\n", grid[0][0], grid[0][1], grid[0][2]);
puts("=========");
printf("%c | %c | %c\n", grid[1][0], grid[1][1], grid[1][2]);
puts("=========");
printf("%c | %c | %c\n", grid[2][0], grid[2][1], grid[2][2]);
}
int gameLoop(char grid[][3], int numPlayers, int difficulty){
char move = '0';
int turn = 1;
char mark = getStartingMark(numPlayers);
clearScreen();
while(turn != 10) {
clearScreen();
drawGrid(grid);
move = getMove(grid, mark, numPlayers, turn, difficulty);
makeMove(grid, move, mark);
if(checkThree(grid)){
if(mark == 'O'){
return 1;
}
else {
return 2;
}
}
mark = switchMark(mark);
turn++;
}
return 0;
}
char getMove(char grid[][3], char mark, int numPlayers, int turn, int difficulty){
bool move_ok = false;
int move;
// get Human player move
if(numPlayers == '2' || (numPlayers == '1' && mark == 'X')){
while(move_ok != 1){
printf("\nIt is %c's turn, place your mark: ", mark);
while((move = getchar()) == '\n');
move_ok = checkMove(grid, move);
if (!move_ok){
puts("Invalid move please try again...");
pressToContinue();
clearScreen();
drawGrid(grid);
}
}
}
// get CPU move
else if (numPlayers == '1' && mark == 'O'){
move = cpuMove(grid, turn, difficulty);
}
return move;
}
void makeMove(char grid[][3], int move, char mark){
/*
* Example place mark at grid position '2' ([0][1]):
*
* Row is 50 - 49 = 1 / 3 = 0.33
* Integer division so this is rounded down towards 0.
* Column = 50 - 49 = 1 % 3 = 1
* Result [0][1] which on the grid is '2'.
*
*/
int row = (move - 49) / 3;
int column = (move - 49) % 3;
grid[row][column] = mark;
}
void pressToContinue(void){
puts("\nPress ENTER to continue");
getchar();
getchar();
}
void resetGrid(char grid[][3]){
size_t column = 0;
size_t row = 0;
char first = '1';
for (row = 0 ; row < 3; row++){
for(column = 0; column < 3 ; column++){
grid[row][column] = first;
first++;
}
}
}
int setDifficulty(int currentDifficulty){
clearScreen();
puts("\n***** SET DIFFICULTY *****\n");
if (currentDifficulty == '1'){
puts("Current difficulty is EASY\n");
} else if (currentDifficulty == '2'){
puts("Current difficulty is HARD\n");
}
puts("1 = Easy");
puts("2 = Hard");
fputs("\nChoose your difficulty: ", stdout);
getchar();
int input = getchar();
if (input == '1'){
puts("Difficulty set to EASY");
pressToContinue();
return '1';
} else if (input == '2'){
puts("Difficulty set to HARD");
pressToContinue();
return '2';
}
else{
puts("\nInvalid choice!");
puts("Difficulty unchanged.");
pressToContinue();
return currentDifficulty;
}
}
int showMenu(){
clearScreen();
puts("\nTIC TAC TOE v2.0 by Reslashd");
puts("\n******* MENU *******\n");
puts("1 = 1 Player vs CPU");
puts("2 = 2 Players");
puts("3 = View scores");
puts("4 = Set difficulty");
puts("5 = Exit");
int choice = getchar();
return choice;
}
void showScores(int n_owins, int n_xwins, int n_draws){
clearScreen();
puts("\n***** SCORES *****");
printf("\nO has %d wins", n_owins);
printf("\nX has %d wins", n_xwins);
printf("\nThere are %d draws\n", n_draws);
pressToContinue();
}
char getStartingMark(int numPlayers){
int start_mark = 0;
puts("\nPlease choose starting player");
if (numPlayers == '1'){
puts("1 = O (CPU)");
} else if (numPlayers == '2'){
puts("1 = O");
}
puts("2 = X");
getchar();
start_mark = getchar();
if (start_mark == '1'){
return 'O';
} else {
return 'X';
}
}
char switchMark(char currentMark){
if (currentMark == 'X'){
return 'O';
} else {
return 'X';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment