diff --git a/code/history.txt b/code/history.txt new file mode 100644 index 0000000..b043934 --- /dev/null +++ b/code/history.txt @@ -0,0 +1,2 @@ +matin,45,ali,18,matin,Thu Jan 1 22:32:39 2026 +matin,15,Bot,20,Bot,Tue Jan 6 11:10:18 2026 diff --git a/code/main.cpp b/code/main.cpp new file mode 100644 index 0000000..4a0b14c --- /dev/null +++ b/code/main.cpp @@ -0,0 +1,702 @@ +// ==============================================Importing Libraries============================== +#include +#include +#include // cout +#include // inputs +#include // random +#include // time +#include // file +#include // delay + +using namespace std; + + +//==============================================Structures & Global variables======================================= +int board_size = 8; + +// --- ANSI Color Codes --- +const string RESET = "\033[0m"; +const string BLACK_TXT = "\033[30m"; // black text +const string RED_TXT = "\033[31m"; // red text +const string GREEN_TXT = "\033[32m"; // green text +const string YELLOW_TXT = "\033[33m"; // yellow text +const string WHITE_TXT = "\033[97m"; // white text + +const string BG_GREEN = "\033[42m"; // green background +const string BG_YELLOW = "\033[43m"; // yellow background +const string BG_DEFAULT = "\033[49m"; // default background + +const string CYAN_TXT = "\033[36m"; +const string MAGENTA_TXT = "\033[35m"; +const string BOLD = "\033[1m"; +const string MARGIN = " "; + +struct ColorChanger{ + int startI, startJ; + int endI, endJ; + int directionX, directionY; +}; + +struct Game{ + int **board; + int turn; + string player1name; + string player2name; + bool isSinglePlayerMode; + int black_count; + int white_count; + ColorChanger color_changer; +}; + +struct CellState{ + const int empty = 0, black = 1, white = 2; +}; + +Game game; +CellState cell_state; +int curserI, curserJ; + +/* + history file format: + player1name,player1discs,player2name,player2discs,winner,gamedate +*/ + +// crowling through a cell's nieghbors +int IndX[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; +int IndY[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; + +//=============================================Function prototypes=============================== +void ShowBoard(int i, int j); +void ShowMenu(); +void GameMode(); // single player mode or two player mode +void PlayGame(); +void PlaceCell(int i, int j); +void SwitchTurn(); +bool CheckValidTurn(int turn); +bool CheckValidMove(int i, int j, bool change_color); +void ChangeColor(); +void ShowHistory(); +void BotMove(); +void SelectBoardSize(); +void AllocateBoard(); +void DeallocateBoard(); + + +//=============================================Main Function===================================== +int main(){ + + system("chcp 65001"); // for showing the unicode characters in terminal + srand(static_cast(time(nullptr))); + + + // preparing board for the start of the game + game.board = nullptr; + + + ShowMenu(); + + + return 0; +} + + + +//============================================Functions========================================= + +// THE GAME +void PlayGame(){ + + // recieving names and declaring turns + system("cls"); + cout << "\n\n"; + cout << MARGIN << BOLD << CYAN_TXT << u8"╔══════════════════════════════════╗" << RESET << endl; + cout << MARGIN << BOLD << CYAN_TXT << u8"║ GAME SETUP ║" << RESET << endl; + cout << MARGIN << BOLD << CYAN_TXT << u8"╚══════════════════════════════════╝" << RESET << endl; + cout << endl; + if(!game.isSinglePlayerMode){ // two player mode + cout << MARGIN << YELLOW_TXT << "Player 1 Name (Black): " << CYAN_TXT; + cin >> game.player1name; + cout << RESET << "\n"; + + cout << MARGIN << YELLOW_TXT << "Player 2 Name (White): " << CYAN_TXT; + cin >> game.player2name; + cout << RESET; + } + else{ + cout << MARGIN << YELLOW_TXT << "Enter Your Name: " << CYAN_TXT; + cin >> game.player1name; + cout << RESET; + game.player2name = "Bot"; + } + game.turn = 1; + + + // moving through the board + bool running = true; + int opposite_turn; + + while(running){ + ShowBoard(curserI, curserJ); + + if(game.isSinglePlayerMode && game.turn == 2){ + Sleep(500); // a delay for palyer to see bot's move + BotMove(); + SwitchTurn(); + } + else { + char pressed_key = getch(); + switch(pressed_key) + { + case 'w': case 'W': + if(curserI > 0) curserI--; + break; + case 's': case 'S': + if(curserI < board_size - 1) curserI++; + break; + case 'd': case 'D': + if(curserJ < board_size - 1) curserJ++; + break; + case 'a': case 'A': + if(curserJ > 0) curserJ--; + break; + + case '\r': // Enter + if(CheckValidMove(curserI, curserJ, true)){ + PlaceCell(curserI, curserJ); + SwitchTurn(); + } + else{ + cout << "\a"; + } + break; + case ' ': // emergency exit + running = false; + // DeallocateBoard(); + break; + } + } + + if(!running) break; + + // end of game or switch turn because of no valid move + opposite_turn = (game.turn == 1 ? 2 : 1); + bool currentHasMove = CheckValidTurn(game.turn); + bool opponentHasMove = CheckValidTurn(opposite_turn); + + if(!currentHasMove && opponentHasMove){ + cout << "\nNo Valid Moves for " << (game.turn == 1 ? game.player1name : game.player2name) << "! Passing turn..."; + getch(); + SwitchTurn(); + } + else if(!currentHasMove && !opponentHasMove){ // end of game + ShowBoard(-1, -1); + time_t now = time(0); + string winnerName = "Tie"; + + if(game.black_count > game.white_count) winnerName = game.player1name; + else if(game.black_count < game.white_count) winnerName = game.player2name; + + cout << "\n========================\n"; + if (winnerName == "Tie") cout << " TIE GAME!"; + else cout << " WINNER: " << winnerName; + cout << "\n========================\n"; + + ofstream history("history.txt", ios::app); + if(history.is_open()){ + + // AI generated + char* dt = ctime(&now); // converting datetime to string + // deleting \n from the end of the time string + string dateStr = string(dt); + if (!dateStr.empty() && dateStr[dateStr.length()-1] == '\n') { + dateStr.erase(dateStr.length()-1); + } + //... + + history << game.player1name << "," << game.black_count << "," + << game.player2name << "," << game.white_count << "," + << winnerName << "," + << dateStr << endl; + + history.close(); + cout << "Game saved to history." << endl; + } else { + cout << "Error saving history!" << endl; + } + + cout << "Press any key to exit..."; + getch(); + running = false; + // DeallocateBoard(); + + // preparing board for the start of the game + for(int i = 0; i < board_size; i++){ + for(int j = 0; j < board_size; j++){ + game.board[i][j] = cell_state.empty; + } + } + game.board[3][3] = cell_state.white; + game.board[4][4] = cell_state.white; + game.board[3][4] = cell_state.black; + game.board[4][3] = cell_state.black; + + + } + } + DeallocateBoard(); + ShowMenu(); +} + +// selecting size of the board (6 or 8 or 10) +void SelectBoardSize(){ + while(true){ + system("cls"); + cout << "\n\n"; + cout << MARGIN << BOLD << CYAN_TXT << u8"╔══════════════════════════════════╗" << RESET <= 0 && x < board_size && y >= 0 && y < board_size && game.board[x][y] == opponent){ + + // going on in the same direction + while(true){ + x += dx; + y += dy; + + if(x < 0 || x >= board_size || y < 0 || y >= board_size) { + break; + } + if(game.board[x][y] == cell_state.empty){ + break; + } + if(game.board[x][y] == myColor){ + + game.color_changer.startI = curserI; + game.color_changer.startJ = curserJ; + game.color_changer.endI = x; + game.color_changer.endJ = y; + game.color_changer.directionX = dx; + game.color_changer.directionY = dy; + if(change_color) ChangeColor(); // if we are checking the cell for a player move then we change the colors int the same process + + validMove = true; + } + } + } + } + return validMove; + +} + +// checking if the player can play a move +bool CheckValidTurn(int turn){ + for(int i = 0; i < board_size; i++){ + for(int j = 0; j < board_size; j++){ + if(game.board[i][j] == cell_state.empty && CheckValidMove(i, j, false)) + return true; + } + } + return false; +} + +// changin cells after a valid move +void ChangeColor(){ + int opposite_color = (game.turn == 1 ? cell_state.black : cell_state.white); + int x = game.color_changer.startI; + int y = game.color_changer.startJ; + while(true){ + x += game.color_changer.directionX; + y += game.color_changer.directionY; + + if(game.board[x][y] != opposite_color){ + game.board[x][y] = opposite_color; + } + else{ + break; + } + + + } +} + +// showing history (crawling through history.txt file which is considered a database for this game) +void ShowHistory(){ + system("cls"); + + cout << "\n\n"; + cout << MARGIN << BOLD << CYAN_TXT << u8"╔══════════════════════════════════════════════════════════════╗" << RESET << endl; + cout << MARGIN << BOLD << CYAN_TXT << u8"║ GAME HISTORY ARCHIVE ║" << RESET << endl; + cout << MARGIN << BOLD << CYAN_TXT << u8"╚══════════════════════════════════════════════════════════════╝" << RESET << endl; + cout << endl; + + ifstream history("history.txt"); + if(!history.is_open()){ + cout << MARGIN << RED_TXT << "No history found or could not open file." << RESET << endl; + cout << "\n" << MARGIN << "Press any key to return..."; + getch(); + ShowMenu(); + return; + } + + cout << MARGIN << YELLOW_TXT << left + << setw(12) << "Player 1" + << setw(8) << "Score" + << setw(12) << "Player 2" + << setw(8) << "Score" + << setw(12) << "Winner" + << setw(20) << "Date" + << RESET << endl; + cout << MARGIN << CYAN_TXT << u8"──────────────────────────────────────────────────────────────────────" << RESET << endl; + + + + string line; + string player1, player2, winner, date; + string player1discs, player2discs; + int splitter[5]; + while(getline(history, line)){ + for(int i = 0; i < 5; i++){ + if(i == 0) + splitter[i] = line.find(","); + else + splitter[i] = line.find(",", splitter[i - 1] + 1); + } + + player1 = line.substr(0, splitter[0]); + player1discs = line.substr(splitter[0] + 1, splitter[1] - splitter[0] - 1); + player2 = line.substr(splitter[1] + 1, splitter[2] - splitter[1] - 1); + player2discs = line.substr(splitter[2] + 1, splitter[3] - splitter[2] - 1); + winner = line.substr(splitter[3] + 1, splitter[4] - splitter[3] - 1); + date = line.substr(splitter[4] + 1); + + cout << MARGIN << left + << CYAN_TXT << setw(12) << player1 + << BOLD << WHITE_TXT << setw(8) << player1discs + << RESET << CYAN_TXT << setw(12) << player2 + << BOLD << WHITE_TXT << setw(8) << player2discs + << RESET << GREEN_TXT << setw(12) << winner + << RESET << setw(20) << date + << endl; + + } + + + history.close(); + cout << "\n\n" << MARGIN << BG_GREEN << BLACK_TXT << " Press any key to return... " << RESET; + getch(); + ShowMenu(); + +} + +void BotMove(){ + + if(CheckValidTurn(game.turn)){ + + // allocating an array for bot's available moves + int **availabe_moves = new int*[board_size*board_size]; + for(int i = 0; i < (board_size*board_size); i++){ + availabe_moves[i] = new int[2]; + } + + + + // adding available moves to the array + int counter = 0; + for(int i = 0; i < board_size; i++){ + for(int j = 0; j < board_size; j++){ + if(CheckValidMove(i, j, false)){ + availabe_moves[counter][0] = i; + availabe_moves[counter][1] = j; + counter++; + } + } + } + + + if (counter > 0) { + int randomIndex = rand() % counter; + + curserI = availabe_moves[randomIndex][0]; + curserJ = availabe_moves[randomIndex][1]; + + CheckValidMove(curserI, curserJ, true); + PlaceCell(curserI, curserJ); + } + + + // deallocating availabe moves array + for(int i = 0; i < board_size*board_size; i++){ + delete[] availabe_moves[i]; + } + delete[] availabe_moves; + + } + else{ + SwitchTurn(); + } + + +} \ No newline at end of file diff --git a/code/othello.exe b/code/othello.exe new file mode 100644 index 0000000..c0ff7dd Binary files /dev/null and b/code/othello.exe differ