commit 9b68b97ae6945783aee5f6e1b7ab11a2c818b8f4 Author: Ramtin_Jafari Date: Fri Apr 17 20:07:57 2026 +0330 Initial commit on develop branch diff --git a/Othello/Game.cpp b/Othello/Game.cpp new file mode 100644 index 0000000..2d804ef --- /dev/null +++ b/Othello/Game.cpp @@ -0,0 +1,83 @@ +#include "codes/Database/GameHistoryManager.h" +#include "codes/Structures/Config.h" +#include "codes/ConfigModifier.h" +#include "codes/GameMaster.h" +#include "codes/GameReporter.h" +#include "codes/GameReviewer.h" +#include "codes/Helper.h" +#include +#include +#include + +int main() +{ + system("cls"); + + Config::initialize(); + syncGameHistoryWithLimit(); + + int chosenOption = 0; + + while (true) + { + system("cls"); + + std::cout << "Welcome to Othello game!" << std::endl + << (chosenOption == 0 ? " > ":" ") << " new game" << std::endl + << (chosenOption == 1 ? " > ":" ") << " load game" << std::endl + << (chosenOption == 2 ? " > ":" ") << " help" << std::endl + << (chosenOption == 3 ? " > ":" ") << " games history" << std::endl + << (chosenOption == 4 ? " > ":" ") << " settings" << std::endl + << (chosenOption == 5 ? " > ":" ") << " exit" << std::endl; + + int userInput = getch(); + + if (userInput == static_cast('w') || userInput == 72) + { + if (chosenOption > 0) + { + chosenOption--; + continue; + } + } + + if (userInput == static_cast('s') || userInput == 80) + { + if (chosenOption < 5) + { + chosenOption++; + continue; + } + } + + if (userInput == 13) + { + if (chosenOption == 0) + { + executeGameMaster(false); + } + else if (chosenOption == 1) + { + executeGameMaster(true); + } + else if (chosenOption == 2) + { + executeHelper(); + } + else if (chosenOption == 3) + { + executeGameReporter(); + } + else if (chosenOption == 4) + { + executeConfigModifier(); + } + else if (chosenOption == 5) + { + break; + } + } + } + + return 0; +} \ No newline at end of file diff --git a/Othello/codes/ConfigModifier.cpp b/Othello/codes/ConfigModifier.cpp new file mode 100644 index 0000000..48a96b6 --- /dev/null +++ b/Othello/codes/ConfigModifier.cpp @@ -0,0 +1,214 @@ +#include "ConfigModifier.h" +#include "Database/ConfigManager.h" +#include "Database/GameHistoryManager.h" +#include +#include +#include +#include + + + +bool madeChanges = false; + + + +void handleGameHistoryLimit(); +void handleBoardSize(); +void handlShowAvailablePlacesToPieces(); + + + +void executeConfigModifier() +{ + int chosenOption = 0; + + while (true) + { + system("cls"); + + std::cout << "current setting are:" << std::endl + << "The number of games inside game history: " << getGameHistoryLimit() << std::endl + << "The size of the board: " << getBoardSize() << std::endl + << "Show available places for pieces: " << (getShowAvailablePlacesForPieces() ? "Yes":"No") << std::endl << std::endl; + + std::cout << "Which of these properties do you wish to change?" << std::endl; + + std::cout << (chosenOption == 0 ? " >":" ") << " The number of games inside game history" << std::endl + << (chosenOption == 1 ? " >":" ") << " The size of the board" << std::endl + << (chosenOption == 2 ? " >":" ") << " Showing avaibale places for pieces" << std::endl + << (chosenOption == 3 ? " >":" ") << " Apply the changes and exit" << std::endl; + + int userInput = getch(); + + if (userInput == static_cast('w') || userInput == 72) + { + if (chosenOption > 0) + { + chosenOption--; + continue; + } + } + + if (userInput == static_cast('s') || userInput == 80) + { + if (chosenOption < 3) + { + chosenOption++; + continue; + } + } + + if (userInput == 13) + { + if (chosenOption == 0) + { + handleGameHistoryLimit(); + } + else if (chosenOption == 1) + { + handleBoardSize(); + } + else if (chosenOption == 2) + { + handlShowAvailablePlacesToPieces(); + } + else if (chosenOption == 3) + { + break; + } + } + } + + system("cls"); + + if (madeChanges == false) std::cout << "No changes has been made\nPress any key to continue"; + else if (madeChanges == true) std::cout << "Please restart the game for changes to be applied to the game\nPress any key to continue"; + + getch(); + system("cls"); + return; +} + + + +void handleGameHistoryLimit() +{ + system("cls"); + + std::cout << "the current value of the number of games inside game history is " + << getGameHistoryLimit() << std::endl + << "Please set the new value: " << std::endl; + + int newValue; + + std::cin >> newValue; + + if (getGameHistoryLimit() == newValue) + { + return handleGameHistoryLimit(); + } + + if (newValue > 100) + { + std::cout << "Inputed number is too big, you might face slow loading when recieving reports or loading games" + << std::endl << "Do you procced? [Y/N]" << std::endl; + + char procceded; + std::cin >> procceded; + + if (!(procceded == 'Y' || procceded == 'y')) + { + return handleGameHistoryLimit(); + } + } + + modifyGameHistoryLimit(newValue); + syncGameHistoryWithLimit(); + std::cout << "Change has been made\nPress any key to continue"; + getch(); + + return; +} + + + +void handleBoardSize() +{ + system("cls"); + + std::cout << "the current value of the size of the board is " + << getBoardSize() << std::endl + << "Please set the new value: " << std::endl; + + int newValue; + + std::cin >> newValue; + + if (getBoardSize() == newValue) + { + return handleBoardSize(); + } + + if (newValue > 50) + { + std::cout << "Inputed number is too big, please set a number lower than or equal to 50\nPress any key to continue" << std::endl; + + getch(); + + return handleBoardSize(); + } + + if (newValue % 2 == 1) + { + std::cout << "Board size cannot be an odd number, please set an even number\nPress any key to continue"; + + getch(); + + return handleBoardSize(); + } + + modifyBoardSize(newValue); + std::cout << "Change has been made\nPress any key to continue"; + getch(); + + return; +} + + + +void handlShowAvailablePlacesToPieces() +{ + system("cls"); + + bool curValue = getShowAvailablePlacesForPieces(); + + std::cout << (curValue ? + "Currently, you will see available places for putting your piece durning the game": + "Currently, you won't see available places for putting your piece durning the game" + ) << std::endl; + + std::cout << "Do you proceed to change the behavour? [Y/N]" << std::endl; + + char procceded; + std::cin >> procceded; + + if (!(procceded == 'Y' || procceded == 'y')) + { + return; + } + + madeChanges = true; + if (curValue) + { + modifyShowAvailablePlacesForPieces(false); + } + else + { + modifyShowAvailablePlacesForPieces(true); + } + + std::cout << "Change has been made\nPress any key to continue"; + getch(); + + return; +} diff --git a/Othello/codes/ConfigModifier.h b/Othello/codes/ConfigModifier.h new file mode 100644 index 0000000..9cddb1a --- /dev/null +++ b/Othello/codes/ConfigModifier.h @@ -0,0 +1,3 @@ +#pragma once + +void executeConfigModifier(); \ No newline at end of file diff --git a/Othello/codes/Database/Config.txt b/Othello/codes/Database/Config.txt new file mode 100644 index 0000000..3d4652b --- /dev/null +++ b/Othello/codes/Database/Config.txt @@ -0,0 +1,10 @@ +{ + GAME_HISTORY_LIMIT = 100 + BOARD_SIZE = 8 + BOARD_COLOR = black + BOARD_BORDER_COLOR = white + SOUND = 0 + SHOW_AVAILABLE_PLACES_FOR_PIECES = 1 + HINT = 0 + HINT_LIMIT = 3 +} diff --git a/Othello/codes/Database/ConfigManager.cpp b/Othello/codes/Database/ConfigManager.cpp new file mode 100644 index 0000000..a5877dc --- /dev/null +++ b/Othello/codes/Database/ConfigManager.cpp @@ -0,0 +1,201 @@ +#include +#include +#include "../Utilities/StrToInt.h" +#include "../utilities/IntToStr.h" +#include "ConfigManager.h" + +using namespace std; + + + +string getElementOfConfig(int index); +int getGameHistoryLimit(); +int getBoardSize(); +std::string getBoardColor(); +std::string getBoardBorderColor(); +bool getSound(); +bool getShowAvailablePlacesForPieces(); +bool getHint(); +int getHintLimit(); +void modifyElementOfConfig(int index, string newValue); +void modifyGameHistoryLimit(int); +void modifyBoardSize(int); +void modifyBoardColor(std::string); +void modifyBoardBorderColor(std::string); +void modifySound(bool); +void modifyShowAvailablePlacesForPieces(bool); +void modifyHint(bool); +void modifyHintLimit(int); + + + +string getElementOfConfig(int index) +{ + if (index > 7) + { + throw 2; // out of bond + } + + ifstream file ("codes/Database/Config.txt"); + if (!file.is_open()) + { + throw 1; // failed to read the file + } + + string line; + + for (int i{0}; i <= index + 1; i++) + { + getline(file, line); + } + + int startingIndex = line.find("=") + 2; + string result = line.substr(startingIndex, line.size()); + + file.close(); + + return result; +} + + +void modifyElementOfConfig(int index, string newValue) +{ + if (index > 7) + { + throw 2; // out of bounds + } + + std::ifstream fileToRead("codes/Database/Config.txt"); + if (!fileToRead.is_open()) + { + throw 1; // failed to read the file + } + + std::string lines[10]; + + for (int i{0}; i < 10; i++) + { + getline(fileToRead, lines[i]); + } + + fileToRead.close(); + + string targetLine = lines[index + 1]; + int startingIndex = targetLine.find("=") + 2; + targetLine = targetLine.substr(0, startingIndex) + newValue; + lines[index + 1] = targetLine; + + std::ofstream fileToWrite ("codes/Database/Config.txt"); + if (!fileToWrite.is_open()) + { + throw 1; // failed to read the file + } + + for (std::string l : lines) + { + fileToWrite << l << "\n"; + } + fileToWrite.close(); +} + + +int getGameHistoryLimit() +{ + return strToInt(getElementOfConfig(0)); +} + + +int getBoardSize() +{ + return strToInt(getElementOfConfig(1)); +} + + +string getBoardColor() +{ + return getElementOfConfig(2); +} + + +string getBoardBorderColor() +{ + return getElementOfConfig(3); +} + + +bool getSound() +{ + int intBool = strToInt(getElementOfConfig(4)); + return static_cast(intBool); +} + + +bool getShowAvailablePlacesForPieces() +{ + int intBool = strToInt(getElementOfConfig(5)); + return static_cast(intBool); +} + + +bool getHint() +{ + int intBool = strToInt(getElementOfConfig(6)); + return static_cast(intBool); +} + + +int getHintLimit() +{ + return strToInt(getElementOfConfig(7)); +} + + +void modifyGameHistoryLimit(int gameHistoryLimit) +{ + modifyElementOfConfig(0, intToStr(gameHistoryLimit)); +} + + +void modifyBoardSize(int boardSize) +{ + modifyElementOfConfig(1, intToStr(boardSize)); +} + + +void modifyBoardColor(string color) +{ + modifyElementOfConfig(2, color); +} + + +void modifyBoardBorderColor(string color) +{ + modifyElementOfConfig(3, color); +} + + +void modifySound(bool sound) +{ + int soundToSave = static_cast(sound); + modifyElementOfConfig(4, intToStr(soundToSave)); +} + + +void modifyShowAvailablePlacesForPieces(bool newValue) +{ + int newValueToSave = static_cast(newValue); + modifyElementOfConfig(5, intToStr(newValueToSave)); +} + + +void modifyHint(bool hint) +{ + int hintToSave = static_cast(hint); + modifyElementOfConfig(6, intToStr(hintToSave)); +} + + +void modifyHintLimit(int hintLimit) +{ + modifyElementOfConfig(7, intToStr(hintLimit)); +} \ No newline at end of file diff --git a/Othello/codes/Database/ConfigManager.h b/Othello/codes/Database/ConfigManager.h new file mode 100644 index 0000000..2a6863e --- /dev/null +++ b/Othello/codes/Database/ConfigManager.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +int getGameHistoryLimit(); +int getBoardSize(); +std::string getBoardColor(); +std::string getBoardBorderColor(); +bool getSound(); +bool getShowAvailablePlacesForPieces(); +bool getHint(); +int getHintLimit(); + +void modifyGameHistoryLimit(int); +void modifyBoardSize(int); +void modifyBoardColor(std::string); +void modifyBoardBorderColor(std::string); +void modifySound(bool); +void modifyShowAvailablePlacesForPieces(bool); +void modifyHint(bool); +void modifyHintLimit(int); \ No newline at end of file diff --git a/Othello/codes/Database/GameHistory.txt b/Othello/codes/Database/GameHistory.txt new file mode 100644 index 0000000..8d5cc42 --- /dev/null +++ b/Othello/codes/Database/GameHistory.txt @@ -0,0 +1,3 @@ +[0][.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|W|B|.|.|.|.|.|.|W|B|B|.|.|.|.|.|W|B|W|.|.|.|.|.|B|.|.|.|.|.|.|B|W|.|.|.|.|.|.|.|.|.|.|.|.|.][2Player][ramtin|W][farzad|W][W][0] +[1][.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|W|B|.|.|.|.|.|B|B|B|B|.|.|.|.|W|W|W|W|.|.|.|.|.|.|B|B|W|.|.|.|.|W|B|B|.|.|.|.|.|.|.|.|.|.|.][2Player][ramtin|W][farzad|B][B][0] +[2][W|W|W|W|W|W|W|W|W|B|B|B|B|W|B|W|W|W|W|W|B|B|W|W|W|B|W|W|W|W|B|W|W|B|W|W|W|W|B|W|W|W|W|W|B|W|W|W|W|B|B|B|W|W|W|W|W|W|W|W|W|W|B|W][1Player][ramtin|B][Hard Bot|3][W][2] diff --git a/Othello/codes/Database/GameHistoryManager.cpp b/Othello/codes/Database/GameHistoryManager.cpp new file mode 100644 index 0000000..2133f72 --- /dev/null +++ b/Othello/codes/Database/GameHistoryManager.cpp @@ -0,0 +1,391 @@ +// a saved game will follow this patter: +// [gameId][.|.|.|.][1Player][PlayerName|PlayerColor][BotName or PlayerName|BotColor or PlayerColor]['B'][0] and winner at the end +// GameBoard mode Player1 Player2 or Bot current turn + +#include "../Structures/Config.h" +#include "../Structures/SinglePlayerGame.h" +#include "../Structures/MultiPlayerGame.h" +#include "../Utilities/StrToInt.h" +#include "../Utilities/IntSqrt.h" +#include "../Utilities/StrToSymbol.h" +#include "GameHistoryManager.h" +#include + +// file managing +void writeTempoToGameHistory(); +void updateLine(int lineIndex, std::string str); +void addLine(std::string str); +void freeLastLine(); + +// internal uses +int getLinesCount(); +std::string getProperty(int propertyId, std::string game); +int getGameId(std::string game); +int getSavedBoardElementCount(std::string board); + +// adding data +void saveGame(SinglePlayerGame* game); +void saveGame(MultiPlayerGame* game); + +// extracting data +std::string getGameById(int gameId); +int getLineIndexById(int gameId); +std::string getLineByLineIndex(int lineIndex); + +// external uses +SinglePlayerGame getSinglePlayerGame(int id); +MultiPlayerGame getMultiPlayerGame(int id); +void syncGameHistoryWithLimit(); +int getFirstGameId(); +int getLastGameId(); + + +//----------------------------------- file managing -------------------------------------------------------------- +void writeTempoToGameHistory() +{ + std::ifstream tempoFileToRead("codes/Database/tempo.txt"); + std::ofstream GameHistoryToWrite("codes/Database/GameHistory.txt"); + + if (!GameHistoryToWrite.is_open() && !tempoFileToRead.is_open()) + { + throw 1; // faild to read file + } + + std::string line; + + while (getline(tempoFileToRead, line)) + { + GameHistoryToWrite << line << "\n"; + } + + tempoFileToRead.close(); + GameHistoryToWrite.close(); +} + + + +void freeLastLine() +{ + std::ifstream GameHistoryToRead("codes/Database/GameHistory.txt"); + std::ofstream tempoFileToWrite("codes/Database/tempo.txt"); + + if (!GameHistoryToRead.is_open() && !tempoFileToWrite.is_open()) + { + throw 1; // faild to read file + } + + std::string line; + + getline(GameHistoryToRead, line); // skipping the first line + + while (getline(GameHistoryToRead, line)) + { + tempoFileToWrite << line << "\n"; + } + + tempoFileToWrite.close(); + GameHistoryToRead.close(); + + writeTempoToGameHistory(); +} + + + +void updateLine(int lineIndex, std::string str) +{ + std::ifstream GameHistoryToRead("codes/Database/GameHistory.txt"); + std::ofstream tempoFileToWrite("codes/Database/tempo.txt"); + + if (!GameHistoryToRead.is_open() && !tempoFileToWrite.is_open()) + { + throw 1; // faild to read file + } + + int curLineIndex = 0; + std::string line; + + while (getline(GameHistoryToRead, line)) + { + if (curLineIndex == lineIndex) + { + tempoFileToWrite << str << "\n"; + } + else + { + tempoFileToWrite << line << "\n"; + } + curLineIndex++; + } + + tempoFileToWrite.close(); + GameHistoryToRead.close(); + + writeTempoToGameHistory(); +} + + +void addLine(std::string str) +{ + std::ofstream GameHistory("codes/Database/GameHistory.txt", std::ios::app); + + GameHistory << str << "\n"; + + GameHistory.close(); +} + + + +//----------------------------------- internal use ----------------------------------------------- +int getLinesCount() +{ + int count = 0; + + std::string line; + std::ifstream fileToRead("codes/Database/GameHistory.txt"); + + if (!fileToRead.is_open()) + { + throw 1; // faild to read file + } + + while (getline(fileToRead, line)) + { + if (!line.empty()) count++; + } + + return count; +} + + +std::string getProperty(int propertyId, std::string game) +{ + for (int i = 0; i < propertyId; i++) + { + int endingIndex = game.find("]"); + game = game.substr(endingIndex + 1, game.size()); + } + + int EndingIndex = game.find("]"); + std::string property = game.substr( + 1, EndingIndex - 1 + ); + + return property; +} + + +int getGameId(std::string game) +{ + return strToInt(getProperty(0, game)); +} + + +void syncGameHistoryWithLimit() +// has to be called when limit changes, so GameHistory.txt stays within its new limit +{ + int curCount = getLinesCount(); + int limit = Config::getInstance() -> GAME_HISTORY_LIMIT; + + if (curCount > limit) + { + int toRemove = curCount - limit; + + for (int i = 0; i < toRemove; i++) + { + freeLastLine(); + } + } +} + + +int getSavedBoardElementCount(std::string board) +{ + int count = 0; + + for (char element : board) + { + if (element == '|') count++; + } + + return count + 1; +} + + +//--------------------------------------------- adding data -------------------------------------------------- +void saveGame(SinglePlayerGame* game) +{ + int thisGameId = game -> id; + std::string gameToSave = game -> retrieveGame(); + + int lineIndex = getLineIndexById(thisGameId); + + if (lineIndex == -1) + { + addLine(gameToSave); + syncGameHistoryWithLimit(); + } + else + { + updateLine(lineIndex, gameToSave); + } +} + + +void saveGame(MultiPlayerGame* game) +{ + int thisGameId = game -> id; + std::string gameToSave = game -> retrieveGame(); + + int lineIndex = getLineIndexById(thisGameId); + + if (lineIndex == -1) + { + addLine(gameToSave); + syncGameHistoryWithLimit(); + } + else + { + updateLine(lineIndex, gameToSave); + } +} + +//--------------------------------------------- extraxting data -------------------------------------------------- +std::string getGameById(int gameId) +{ + std::string line; + std::ifstream fileToRead("codes/Database/GameHistory.txt"); + + if (!fileToRead.is_open()) + { + throw 1; // faild to read file + } + + while (getline(fileToRead, line)) + { + if (getGameId(line) == gameId) + { + return line; + } + } + + return ""; +} + + +int getLineIndexById(int gameId) +{ + std::string line; + std::ifstream fileToRead("codes/Database/GameHistory.txt"); + + if (!fileToRead.is_open()) + { + throw 1; // faild to read file + } + + int id = 0; + while (getline(fileToRead, line)) + { + if (getGameId(line) == gameId) + { + return id; + } + id++; + } + + return -1; +} + + +std::string getLineByLineIndex(int lineIndex) +{ + std::string line; + std::ifstream fileToRead("codes/Database/GameHistory.txt"); + + if (!fileToRead.is_open()) + { + throw 1; // faild to read file + } + + int linesCount = getLinesCount(); + for (int i = 0; i < linesCount; i++) + { + getline(fileToRead, line); + if (i == lineIndex) return line; + } + + return ""; +} +//-------------------------------------------- external uses ----------------------------------------- +int getFirstGameId() +{ + int liensCount = getLinesCount(); + + if (liensCount == 0) return -1; // file is empty + + std::string game = getLineByLineIndex(0); + return getGameId(game); +} + +int getLastGameId() +{ + int liensCount = getLinesCount(); + + if (liensCount == 0) return -1; // file is empty + + std::string game = getLineByLineIndex(liensCount - 1); + return getGameId(game); +} + + +SinglePlayerGame getSinglePlayerGame(int id) +{ + std::string savedGame = getGameById(id); + + std::string savedBoard = getProperty(1, savedGame); + int boardSize = intSqrt( + getSavedBoardElementCount(savedBoard) + ); + Board board {boardSize}; + board.loadBoard(savedBoard); + + Player player {"", 0}; + player.loadPlayer(getProperty(3, savedGame)); + + Bot bot {"", 0}; + bot.loadBot(getProperty(4, savedGame)); + + char turn = strToSymbol(getProperty(5, savedGame)); + + SinglePlayerGame game {&board, &player, &bot, turn}; + + game.id = id; + + return game; +} + + +MultiPlayerGame getMultiPlayerGame(int id) +{ + std::string savedGame = getGameById(id); + + std::string savedBoard = getProperty(1, savedGame); + int boardSize = intSqrt( + getSavedBoardElementCount(savedBoard) + ); + Board board {boardSize}; + board.loadBoard(savedBoard); + + Player player1 {"", 0}; + player1.loadPlayer(getProperty(3, savedGame)); + + Player player2 {"", 0}; + player2.loadPlayer(getProperty(4, savedGame)); + + char turn = strToSymbol(getProperty(5, savedGame)); + + MultiPlayerGame game {&board, &player1, &player2, turn}; + + game.id = id; + + return game; +} \ No newline at end of file diff --git a/Othello/codes/Database/GameHistoryManager.h b/Othello/codes/Database/GameHistoryManager.h new file mode 100644 index 0000000..432fc2f --- /dev/null +++ b/Othello/codes/Database/GameHistoryManager.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include "../Structures/MultiPlayerGame.h" +#include "../Structures/SinglePlayerGame.h" + +void saveGame(SinglePlayerGame* game); +void saveGame(MultiPlayerGame* game); +SinglePlayerGame getSinglePlayerGame(int id); +MultiPlayerGame getMultiPlayerGame(int id); +std::string getGameById(int gameId); +void syncGameHistoryWithLimit(); +std::string getProperty(int propertyId, std::string game); +int getSavedBoardElementCount(std::string board); +int getGameId(std::string game); +int getFirstGameId(); +int getLastGameId(); \ No newline at end of file diff --git a/Othello/codes/Database/GameLog.txt b/Othello/codes/Database/GameLog.txt new file mode 100644 index 0000000..f9a009e --- /dev/null +++ b/Othello/codes/Database/GameLog.txt @@ -0,0 +1,2 @@ +1 3 B +1 4 W diff --git a/Othello/codes/Database/GameLogManager.cpp b/Othello/codes/Database/GameLogManager.cpp new file mode 100644 index 0000000..6ee0251 --- /dev/null +++ b/Othello/codes/Database/GameLogManager.cpp @@ -0,0 +1,77 @@ +#include "../Structures/Move.h" +#include +#include + + + +void clearGameLog(); +void addLog(Move move); +int countLogs(); +Move getLog(int logIndex); + + + +void clearGameLog() +{ + std::ofstream file ("codes/Database/GameLog.txt", std::ios::trunc); + if (!file.is_open()) + { + throw 1; // failed to read file + } + + file.close(); +} + + +void addLog(Move move) +{ + std::ofstream file("codes/Database/GameLog.txt", std::ios::app); + if (!file.is_open()) + { + throw 1; // failed to read file + } + + file << move.retrieveMove() << "\n"; + + file.close(); +} + + +int countLogs() +{ + int logCount = 0; + std::ifstream file("codes/Database/GameLog.txt"); + std::string line; + + while(std::getline(file, line)) + { + if (line.empty()) continue; + + logCount++; + } + + return logCount; +} + + +Move getLog(int logIndex) +{ + std::ifstream file ("codes/Database/GameLog.txt"); + if (!file.is_open()) + { + throw 1; // failed to read the file + } + + std::string line; + + for (int i{0}; i <= logIndex; i++) + { + std::getline(file, line); + } + + Move move{0, 0, '0'}; + + move.loadMove(line); + + return move; +} \ No newline at end of file diff --git a/Othello/codes/Database/GameLogManager.h b/Othello/codes/Database/GameLogManager.h new file mode 100644 index 0000000..e4b8621 --- /dev/null +++ b/Othello/codes/Database/GameLogManager.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../Structures/Move.h" + +void clearGameLog(); +void addLog(Move move); +int countLogs(); +Move getLog(int logIndex); \ No newline at end of file diff --git a/Othello/codes/Database/tempo.txt b/Othello/codes/Database/tempo.txt new file mode 100644 index 0000000..8d5cc42 --- /dev/null +++ b/Othello/codes/Database/tempo.txt @@ -0,0 +1,3 @@ +[0][.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|W|B|.|.|.|.|.|.|W|B|B|.|.|.|.|.|W|B|W|.|.|.|.|.|B|.|.|.|.|.|.|B|W|.|.|.|.|.|.|.|.|.|.|.|.|.][2Player][ramtin|W][farzad|W][W][0] +[1][.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|W|B|.|.|.|.|.|B|B|B|B|.|.|.|.|W|W|W|W|.|.|.|.|.|.|B|B|W|.|.|.|.|W|B|B|.|.|.|.|.|.|.|.|.|.|.][2Player][ramtin|W][farzad|B][B][0] +[2][W|W|W|W|W|W|W|W|W|B|B|B|B|W|B|W|W|W|W|W|B|B|W|W|W|B|W|W|W|W|B|W|W|B|W|W|W|W|B|W|W|W|W|W|B|W|W|W|W|B|B|B|W|W|W|W|W|W|W|W|W|W|B|W][1Player][ramtin|B][Hard Bot|3][W][2] diff --git a/Othello/codes/GameMaster.cpp b/Othello/codes/GameMaster.cpp new file mode 100644 index 0000000..6b1f55d --- /dev/null +++ b/Othello/codes/GameMaster.cpp @@ -0,0 +1,483 @@ +#include +#include +#include +#include +#include "GameMaster.h" +#include "Database/GameLogManager.h" +#include "GameReviewer.h" +#include "Database/GameHistoryManager.h" +#include "Structures/Board.h" +#include "Structures/Player.h" +#include "Structures/Bot.h" +#include "Structures/SinglePlayerGame.h" +#include "Structures/MultiPlayerGame.h" +#include "Structures/Config.h" +#include "Utilities/outputDecoratedSavedGame.h" +#include "Utilities/GetSavedBoardSize.h" +#include + +void handleLoadGame(); +void handleNewGame(); +void loadSinglePlayerGame(int gameId); +void loadMultiPlayerGame(int gameId); +void handleSinglePlayerNewGame(); +void handleMultiPlayerNewGame(); + +void executeGameMaster(bool loadGameMode) +{ + clearGameLog(); // emptying GameLog for the new game + + if (loadGameMode) handleLoadGame(); + else handleNewGame(); +} + + + +void handleLoadGame() +{ + system("cls"); + + std::cout << "You've chosen to load a previous unfinished game\n" + << "Previous unfunished games are: \n"; + + int startingGameId = getFirstGameId(); + int lastGameId = getLastGameId(); + + if (startingGameId == -1) + { + std::cout << "\n There are no games inside game history\n Press any key to continue"; + getch(); + return; + } + + for (int i = startingGameId; i <= lastGameId; i++) + { + if (getProperty(6, getGameById(i)) == "0") + { + outputDecoratedSavedGame(getGameById(i)); + } + } + + int gameId; + std::string wantedGame; + while (true) + { + std::cout << "\n Please type the id of the game you wish to continue playing: "; + + std::cin >> gameId; + + std::string savedGame = getGameById(gameId); + + if (getProperty(6, savedGame) != "0") + { + std::cout << "The id entered belong to a finished game, please choose an id from the given list\n"; + continue; + } + + wantedGame = savedGame; + break; + } + + if (getProperty(2, wantedGame) == "1Player") + { + loadSinglePlayerGame(gameId); + } + else + { + loadMultiPlayerGame(gameId); + } +} + + + +void loadSinglePlayerGame(int gameId) +{ + int id = getSinglePlayerGame(gameId).id; + Board board = *(getSinglePlayerGame(gameId).GameBoard); + Player player = *(getSinglePlayerGame(gameId).Player1); + Bot bot = *(getSinglePlayerGame(gameId).GameBot); + char CurrentTurnColor = getSinglePlayerGame(gameId).CurrentTurnColor; + + SinglePlayerGame game {&board, &player, &bot, CurrentTurnColor}; + game.id = id; + game.Winner = 0; + game.mode = "1Player"; + + game.start(); + + std::cout << "\n The game has finished, Press any key to continue" << std::endl; + + getch(); + + game.GameBoard -> deleteBoardMemory(); +} + + + +void loadMultiPlayerGame(int gameId) +{ + int id = getMultiPlayerGame(gameId).id; + Board board = *(getMultiPlayerGame(gameId).GameBoard); + Player player1 = *(getMultiPlayerGame(gameId).Player1); + Player player2 = *(getMultiPlayerGame(gameId).Player2); + char CurrentTurnColor = getMultiPlayerGame(gameId).CurrentTurnColor; + + MultiPlayerGame game {&board, &player1, &player2, CurrentTurnColor}; + game.id = id; + game.Winner = 0; + game.mode = "2Player"; + + game.start(); + + std::cout << "\n The game has finished, Press any key to continue" << std::endl; + + getch(); + + game.GameBoard -> deleteBoardMemory(); +} + + + +void handleNewGame() +{ + int chosenOption = 0; + while (true) + { + system("cls"); + + std::cout << (chosenOption == 0 ? " > ":" ") << " Single Player" << std::endl + << (chosenOption == 1 ? " > ":" ") << " MultiPlayer" << std::endl; + + int userInput = getch(); + + if (userInput == static_cast('w') || userInput == 72) + { + if (chosenOption == 1) + { + chosenOption--; + continue; + } + } + + if (userInput == static_cast('s') || userInput == 80) + { + if (chosenOption == 0) + { + chosenOption++; + continue; + } + } + + if (userInput == 13) + { + if (chosenOption == 0) + { + handleSinglePlayerNewGame(); + } + else if (chosenOption == 1) + { + handleMultiPlayerNewGame(); + } + break; + } + } +} + + + +void handleSinglePlayerNewGame() +{ + Bot bot{"0", 0}; + + int chosenOption = 0; + while (true) + { + system("cls"); + + std::cout << "Please choose the difficulty: \n" + << (chosenOption == 0 ? " > ":" ") << " Easy" << std::endl + << (chosenOption == 1 ? " > ":" ") << " Medium" << std::endl + << (chosenOption == 2 ? " > ":" ") << " Hard" << std::endl; + + int userInput = getch(); + + if (userInput == static_cast('w') || userInput == 72) + { + if (chosenOption != 0) + { + chosenOption--; + continue; + } + } + + if (userInput == static_cast('s') || userInput == 80) + { + if (chosenOption != 2) + { + chosenOption++; + continue; + } + } + + if (userInput == 13) + { + if (chosenOption == 0) + { + bot.difficulty = 1; + bot.name = "Easy Bot"; + } + else if (chosenOption == 1) + { + bot.difficulty = 2; + bot.name = "Medium Bot"; + } + else if (chosenOption == 2) + { + bot.difficulty = 3; + bot.name = "Hard Bot"; + } + break; + } + } + + Player player{"0", 'W'}; + while (true) + { + system("cls"); + + std::string name; + std::cout << "Please set your name: "; + std::cin >> name; + std::cout << std::endl; + + if (name.find("|") != -1 || name.find("[") != -1 || name.find("]") != -1) + { + std::cout << "One day you will die" + << "\nSome poeple will attend your funeral only for the food" + << "\nYour friends will hang out again after 1 month" + << "\nYour kids will go to work casually after 2 months" + << "\nYour wife/husband will laugh with a comedy movie after 6 month" + << "\nEventually, your name will be forgotten and memories of you fade away" + << "\nYet some poeple like you think they can escape this fact by putting | or [ or ] inside their names" + << "\nYou guys think noone is going to forget your name because they be like:" + << "\n Damn bro this guy has a | or [ or ] inside their name! I'm going to remember this name till my last breath" + << "\nLet's be real, nobody will say that unless they're high and high poeple can't remember anything more than 5 seconds" + << "\nSo stop annoying me and choose a name without | or [ or ]" + << "\nNow click any freaking key to continue..."; + + getch(); + continue; + } + + player.name = name; + + system("cls"); + break; + } + + chosenOption = 0; + while (true) + { + system("cls"); + + std::cout << "Please choose your color\n" + << (chosenOption == 0 ? " > ":" ") << " Black" << std::endl + << (chosenOption == 1 ? " > ":" ") << " White" << std::endl; + + int userInput = getch(); + + if (userInput == static_cast('w') || userInput == 72) + { + if (chosenOption == 1) + { + chosenOption--; + continue; + } + } + + if (userInput == static_cast('s') || userInput == 80) + { + if (chosenOption == 0) + { + chosenOption++; + continue; + } + } + + if (userInput == 13) + { + if (chosenOption == 0) + { + player.color = 'B'; + } + else if (chosenOption == 1) + { + player.color = 'W'; + } + break; + } + } + + system("cls"); + + Board board{Config::instance -> BOARD_SIZE}; + + board.newGameSetup(); + + SinglePlayerGame game{&board, &player, &bot, 'B'}; + + game.start(); + + std::cout << "\n The game has finished, would you like to see the replay? [Y/N]" << std::endl; + + char procceded; + std::cin >> procceded; + + if (procceded == 'Y' || procceded == 'y') + { + executeGameReviewer(*game.GameBoard); + } + + game.GameBoard -> deleteBoardMemory(); +} + + + +void handleMultiPlayerNewGame() +{ + Player player1{"0", 'W'}; + while (true) + { + system("cls"); + + std::string name; + std::cout << "Player1, Please set your name: "; + std::cin >> name; + std::cout << std::endl; + + if (name.find("|") != -1 || name.find("[") != -1 || name.find("]") != -1) + { + std::cout << "One day you will die" + << "\nSome poeple will attend your funeral only for the food" + << "\nYour friends will hang out again after 1 month" + << "\nYour kids will go to work casually after 2 months" + << "\nYour wife/husband will laugh with a comedy movie after 6 month" + << "\nEventually, your name will be forgotten and memories of you fade away" + << "\nYet some poeple like you think they can escape this fact by putting | or [ or ] inside their names" + << "\nYou guys think noone is going to forget your name because they be like:" + << "\n Damn bro this guy has a | or [ or ] inside their name! I'm going to remember this name till my last breath" + << "\nLet's be real, nobody will say that unless they're high and high poeple can't remember anything more than 5 seconds" + << "\nSo stop annoying me and choose a name without | or [ or ]" + << "\nNow click any freaking key to continue..."; + + getch(); + continue; + } + + player1.name = name; + + system("cls"); + break; + } + + int chosenOption = 0; + while (true) + { + system("cls"); + + std::cout << "Player1, please choose your color\n" + << (chosenOption == 0 ? " > ":" ") << " Black" << std::endl + << (chosenOption == 1 ? " > ":" ") << " White" << std::endl; + + int userInput = getch(); + + if (userInput == static_cast('w') || userInput == 72) + { + if (chosenOption == 1) + { + chosenOption--; + continue; + } + } + + if (userInput == static_cast('s') || userInput == 80) + { + if (chosenOption == 0) + { + chosenOption++; + continue; + } + } + + if (userInput == 13) + { + if (chosenOption == 0) + { + player1.color = 'B'; + } + else if (chosenOption == 1) + { + player1.color = 'W'; + } + break; + } + } + + Player player2{"0", 'W'}; + while (true) + { + system("cls"); + + std::string name; + std::cout << "Player2, Please set your name: "; + std::cin >> name; + std::cout << std::endl; + + if (name.find("|") != -1 || name.find("[") != -1 || name.find("]") != -1) + { + std::cout << "One day you will die" + << "\nSome poeple will attend your funeral only for the food" + << "\nYour friends will hang out again after 1 month" + << "\nYour kids will go to work casually after 2 months" + << "\nYour wife/husband will laugh with a comedy movie after 6 month" + << "\nEventually, your name will be forgotten and memories of you fade away" + << "\nYet some poeple like you think they can escape this fact by putting | or [ or ] inside their names" + << "\nYou guys think noone is going to forget your name because they be like:" + << "\n Damn bro this guy has a | or [ or ] inside their name! I'm going to remember this name till my last breath" + << "\nLet's be real, nobody will say that unless they're high and high poeple can't remember anything more than 5 seconds" + << "\nSo stop annoying me and choose a name without | or [ or ]" + << "\nNow click any freaking key to continue..."; + + getch(); + continue; + } + + player2.name = name; + player2.color = (player1.color == 'W' ? 'B':'W'); + + system("cls"); + break; + } + + system("cls"); + + Board board{Config::getInstance() -> BOARD_SIZE}; + + board.newGameSetup(); + + MultiPlayerGame game{&board, &player1, &player2, 'B'}; + + game.start(); + + std::cout << "\n The game has finished, would you like to see the replay? [Y/N]" << std::endl; + + char procceded; + std::cin >> procceded; + + if (procceded == 'Y' || procceded == 'y') + { + executeGameReviewer(*game.GameBoard); + } + + game.GameBoard -> deleteBoardMemory(); +} \ No newline at end of file diff --git a/Othello/codes/GameMaster.h b/Othello/codes/GameMaster.h new file mode 100644 index 0000000..41e0b58 --- /dev/null +++ b/Othello/codes/GameMaster.h @@ -0,0 +1,3 @@ +#pragma once + +void executeGameMaster(bool loadGameMode); \ No newline at end of file diff --git a/Othello/codes/GameReporter.cpp b/Othello/codes/GameReporter.cpp new file mode 100644 index 0000000..30453c9 --- /dev/null +++ b/Othello/codes/GameReporter.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include "GameReporter.h" +#include "Structures/Config.h" +#include "DataBase/GameHistoryManager.h" +#include "Utilities/outputDecoratedSavedGame.h" + + +void executeGameReporter() +{ + system("cls"); + + std::cout << "Here is the history of the last " + << Config::getInstance() -> GAME_HISTORY_LIMIT + << " Played Games" << std::endl + << "If you wish to change the number of saved games inside the history, you can change this number inside settings section of the game"; + + int firstId = getFirstGameId(); + int lastId = getLastGameId(); + + if (firstId == -1) + { + std::cout << "\n There are no games inside game history\n Press any key to continue"; + getch(); + return; + } + + for (int id = lastId; id >= firstId; id--) + { + outputDecoratedSavedGame(getGameById(id)); + } + + std::cout << "\nPress any key to continue"; + getch(); +} \ No newline at end of file diff --git a/Othello/codes/GameReporter.h b/Othello/codes/GameReporter.h new file mode 100644 index 0000000..cd1b71a --- /dev/null +++ b/Othello/codes/GameReporter.h @@ -0,0 +1,3 @@ +#pragma once + +void executeGameReporter(); \ No newline at end of file diff --git a/Othello/codes/GameReviewer.cpp b/Othello/codes/GameReviewer.cpp new file mode 100644 index 0000000..7121f62 --- /dev/null +++ b/Othello/codes/GameReviewer.cpp @@ -0,0 +1,63 @@ +#include "GameReviewer.h" +#include "Database/GameLogManager.h" +#include "Structures/Move.h" +#include "Structures/Board.h" +#include "conio.h" +#include +#include + +void executeGameReviewer(Board board) +{ + board.CursorX = -1; + board.CursorY = -1; + + int logsCount = countLogs(); + + int logId = -1; + + while (true) + { + system("cls"); + + std::cout << "Press any key for next move, ESC to exit...\n"; + + + if (logId == -1) + { + board.newGameSetup(); + board.prepareBoardForMove('W'); + } + else + { + Move move = getLog(logId); + board.putPiece(move.x, move.y, move.color); + if (move.color == 'B') + { + board.prepareBoardForMove('W'); + } + else + { + board.prepareBoardForMove('B'); + } + } + + board.display(); + + int userInput = _getch(); + + if(userInput == 27) + { + break; + } + else + { + if (logId == logsCount - 1) + { + break; + } + logId++; + } + } + + system("cls"); +} \ No newline at end of file diff --git a/Othello/codes/GameReviewer.h b/Othello/codes/GameReviewer.h new file mode 100644 index 0000000..400cdcc --- /dev/null +++ b/Othello/codes/GameReviewer.h @@ -0,0 +1,5 @@ +#pragma once + +#include "Structures/Board.h" + +void executeGameReviewer(Board board); \ No newline at end of file diff --git a/Othello/codes/Helper.cpp b/Othello/codes/Helper.cpp new file mode 100644 index 0000000..d8f3823 --- /dev/null +++ b/Othello/codes/Helper.cpp @@ -0,0 +1,8 @@ +#include "Helper.h" +#include "Structures/Board.h" +#include + +void executeHelper() +{ + +} \ No newline at end of file diff --git a/Othello/codes/Helper.h b/Othello/codes/Helper.h new file mode 100644 index 0000000..9a69cd8 --- /dev/null +++ b/Othello/codes/Helper.h @@ -0,0 +1,3 @@ +#pragma once + +void executeHelper(); \ No newline at end of file diff --git a/Othello/codes/Structures/Board.cpp b/Othello/codes/Structures/Board.cpp new file mode 100644 index 0000000..907fce7 --- /dev/null +++ b/Othello/codes/Structures/Board.cpp @@ -0,0 +1,422 @@ +#include "Config.h" +#include "Board.h" +#include "Move.h" +#include "../Database/GameLogManager.h" +#include "../Utilities/SymbolToStr.h" +#include + + +//------------------------------- Constructor & deconstructor ---------------------------------------- + +Board::Board(int boardSize) +// grid holds the matrix used for game logic +// validMovesGrid is only used to show valid moves +// display grid is used to potentially combine grid and validMovesGrid for displaying the board +{ + BoardSize = boardSize; + + grid = new char*[boardSize]; + for (int i = 0; i < boardSize; ++i) { + grid[i] = new char[boardSize]; + } + + validMovesGrid = new char*[boardSize]; + for (int i = 0; i < boardSize; ++i) { + validMovesGrid[i] = new char[boardSize]; + } + + displayGrid = new char*[boardSize]; + for (int i = 0; i < boardSize; ++i) { + displayGrid[i] = new char[boardSize]; + } + + for (int y = 0; y < boardSize; y++) + { + for (int x = 0; x < boardSize; x++) + { + grid[y][x] = '.'; + validMovesGrid[y][x] = '.'; + displayGrid[y][x] = '.'; + } + } +} + + +void Board::deleteBoardMemory() +// when the game is over and maybe GameReviewer was executed +// this method has to be called to free up the allocated memory +// a destructor could do the job too +{ + for (int i = 0; i < BoardSize; ++i) + { + delete[] grid[i]; + } + delete[] grid; + + for (int i = 0; i < BoardSize; ++i) + { + delete[] validMovesGrid[i]; + } + delete[] validMovesGrid; + + for (int i = 0; i < BoardSize; ++i) + { + delete[] displayGrid[i]; + } + delete[] displayGrid; +} + + +//-------------------------------- Database & pre-game operations ------------------------------------ + +std::string Board::retrieveBoard() +// turns grid into string format to be saved inside database +// this string can be used to create a grid using loadBoard method +{ + int size = BoardSize * BoardSize + BoardSize; + std::string result = ""; + + for (int y = 0; y < BoardSize; y++) + { + for (int x = 0; x < BoardSize; x++) + { + result += symbolToStr(grid[y][x]); + + if (!(y == BoardSize-1 && x == BoardSize-1)) + { + result += "|"; + } + } + } + + return result; +} + + +void Board::newGameSetup() +// grid represents the starting state of the board when playing a new game +// called for board before passing its pointer for creating a SinglePlayerGame/MultiPlayerGame instance +{ + for (int y = 0; y < BoardSize; y++) + { + for (int x = 0; x < BoardSize; x++) + { + grid[y][x] = '.'; + } + } + + grid[BoardSize/2 - 1][BoardSize/2 - 1] = 'W'; + grid[BoardSize/2][BoardSize/2] = 'W'; + grid[BoardSize/2][BoardSize/2 - 1] = 'B'; + grid[BoardSize/2 - 1][BoardSize/2] = 'B'; + + updateDisplayGrid(); +} + + +void Board::loadBoard(std::string Board) +// recreates grid based on a string inside database +// this string is created by retrieveBoard method +{ + int index = 0; + + for (int y = 0; y < BoardSize; y++) + { + for (int x = 0; x < BoardSize; x++) + { + grid[y][x] = static_cast(Board[index]); + index += 2; + } + } + + updateDisplayGrid(); +} + + +// ------------------------------------- grid and game operations ----------------------------------------- + +void Board::prepareBoardForMove(char thisTurnColor) +// this method has to be called when a valid move is made +// updated displayGrid with the latest changes in grid +// validMovesGrid will find valid moves based on the new grid +// if user has chosen to see valid moves inside the board (SHOW_AVAILABLE_PLACES_FOR_PIECES is set to true) +// then they are putted inside the displayGrid (combining grid and validMovesGrid) +{ + updateDisplayGrid(); + prepareValidMovesGrid(thisTurnColor); + if (Config::getInstance() -> SHOW_AVAILABLE_PLACES_FOR_PIECES) + putValidMoves(); +} + + +void Board::flip(int x, int y) +// changes the color of the piece inside the given coordinates +{ + if (grid[y][x] == 'B') + grid[y][x] = 'W'; + else if (grid[y][x] == 'W') + grid[y][x] = 'B'; +} + + +void Board::putPiece(int x, int y, char color) +{ + grid[y][x] = color; + + Move move(x, y, color); + addLog(move); + + // each comnination of coresponding elements represent a diraction + // left-up & left & left-down & up & down & right-uo & right & right-down + const int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1}; + const int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1}; + + for (int d = 0; d < 8; ++d) + { + int flips = countFlipsInDirection(x, y, dx[d], dy[d], color); + + if (flips > 0) + { + int xc = x + dx[d]; + int yc = y + dy[d]; + + for (int step = 0; step < flips; ++step) + { + flip(xc, yc); + xc += dx[d]; + yc += dy[d]; + } + } + } +} + + +int Board::countBlack() +// counts the black pieces of the Board +{ + int count = 0; + for (int y = 0; y < BoardSize; y++) + { + for (int x = 0; x < BoardSize; x++) + { + if (grid[y][x] == 'B') count++; + } + } + + return count; +} + +int Board::countWhite() +// counts the white pieces of the Board +{ + int count = 0; + for (int y = 0; y < BoardSize; y++) + { + for (int x = 0; x < BoardSize; x++) + { + if (grid[y][x] == 'W') count++; + } + } + + return count; +} + + +//------------------------------------------ validGrid operations -------------------------------------------- + +int Board::countValidMoves() +// returns the number of valid moves that can be made for the current turn +// should be called when validMovesGrid is created for the turn +// in a nutshell, after prepareBoardForMove method is called +// used to decide of a game has to end +{ + int count = 0; + + for (int y = 0; y < BoardSize; y++) + { + for (int x = 0; x < BoardSize; x++) + { + if (validMovesGrid[y][x] == 'O') count++; + } + } + + return count; +} + + + +int Board::isValid(int x, int y, char color) +// returns the number of gains (opponent pieces fliped) if a piece with the given color is put in the given coords +// if it returns 0, then the move is inValid +// used as the primary parameter for the medium bot +{ + if (grid[y][x] != '.') return 0; + + int totalFlips = 0; + + // each comnination of coresponding elements represent a diraction + // left-up & left & left-down & up & down & right-uo & right & right-down + const int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1}; + const int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1}; + + for (int d = 0; d < 8; d++) + { + totalFlips += countFlipsInDirection(x, y, dx[d], dy[d], color); + } + + return totalFlips; +} + + +int Board::countFlipsInDirection(int x, int y, int dx, int dy, char color) +// counts the number of flips happened by putting a piece with the given color inside the given coordinates +// dx and dy represent the direction and more specificly, the steps of x and y +{ + char oppColor = (color == 'B' ? 'W' : 'B'); + + int flips = 0; + int xc = x + dx; + int yc = y + dy; + + + while ((xc >= 0 && xc < BoardSize) && (yc >= 0 && yc < BoardSize) && grid[yc][xc] == oppColor) + { + flips++; + xc += dx; + yc += dy; + } + + // if loop doesn't meet a same colored piece, one of x coords or y coords will get out of bond + // or the element loop breaked for is whether a '.' (empty place) + if ((xc < 0 || xc == BoardSize) || (yc < 0 || yc == BoardSize) || grid[yc][xc] != color) + { + return 0; + } + + return flips; +} + + +void Board::resetValidMovesGrid() +// clears validGrid elements to empty places +{ + for (int y = 0; y < BoardSize; y++) + { + for (int x = 0; x < BoardSize; x++) + { + validMovesGrid[y][x] = '.'; + } + } +} + + +void Board::prepareValidMovesGrid(char color) +// first ValidMovesGrid resets itself so previous data won't interupt the process +// then check each place in grid, if it's valid, it'll put O inside the coresponding coordinates inside itself +{ + resetValidMovesGrid(); + + for (int y = 0; y < BoardSize; y++) + { + for (int x = 0; x < BoardSize; x++) + { + if (isValid(x, y, color) != 0) + validMovesGrid[y][x] = 'O'; + } + } +} + + +//----------------------------------- display and displayGrid operations ----------------------------------------- +void Board::updateDisplayGrid() +// displayGrid be copied based on the elements (pieces) inside grid +{ + for (int y = 0; y < BoardSize; y++) + { + for (int x = 0; x < BoardSize; x++) + { + displayGrid[y][x] = grid[y][x]; + } + } +} + + +void Board::putValidMoves() +// validMovesGrid has the valid moves coordinates inside itself +// by calling this method, displayGrid will mark valid moves which +// validMovesGrid marks +{ + for (int y = 0; y < BoardSize; y++) + { + for (int x = 0; x < BoardSize; x++) + { + if (validMovesGrid[y][x] == 'O') + { + displayGrid[y][x] = 'O'; + } + } + } +} + + +void Board::display() +// outputs displayGrid with viasual decorations +{ + std::cout << " "; + for (int i = 1; i <= BoardSize; i++) + { + std::cout << i << " "; + } + std::cout << std::endl; + + std::cout << " "; + std::cout << (char)201; + for (int i = 0; i < BoardSize; i++) + { + std::cout << (char)205 << (char)205 << (char)205; + } + std::cout << (char)187 << std::endl; + + for (int y = 0; y < BoardSize; y++) + { + std::cout << y + 1 << " " << (char)186; + for (int x = 0; x < BoardSize; x++) + { + if (y == CursorY && x == CursorX) std::cout << "["; + else std::cout << " "; + + char element = displayGrid[y][x]; + if (element == 'W') // white piece + { + std::cout << 'X'; + } + else if (element == 'B') // black piece + { + std::cout << 'O'; + } + else if (element == '.') // empty space + { + std::cout << ' '; + } + else if (element == 'O') // valid move + { + std::cout << (char)249; // ∙ + } + + if (y == CursorY && x == CursorX) std::cout << "]"; + else std::cout << " "; + } + std::cout << (char)186 << std::endl; + } + + std::cout << " "; + std::cout << (char)200; + for (int i = 0; i < BoardSize; i++) + { + std::cout << (char)205 << (char)205 << (char)205; + } + std::cout << (char)188 << std::endl; +} +// (char)186 = ║ (char)200 = ╚ (char)188 = ╝ (char)205 = ═ (char)206 = ╦ (char)202 = ╩ (char)187 = ╗ (char)201 = ╔ +// (char)79 = O +// (char)254 = ■ \ No newline at end of file diff --git a/Othello/codes/Structures/Board.h b/Othello/codes/Structures/Board.h new file mode 100644 index 0000000..2976bb3 --- /dev/null +++ b/Othello/codes/Structures/Board.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +struct Board +{ + int BoardSize; + char** grid; + char** validMovesGrid; + char** displayGrid; + int CursorX = 0; + int CursorY = 0; + +public: + Board(int boardSize); + void deleteBoardMemory(); + std::string retrieveBoard(); + void newGameSetup(); + void loadBoard(std::string Board); + void prepareBoardForMove(char thisTurnColor); + void putPiece(int x, int y, char color); + int countBlack(); + int countWhite(); + int isValid(int x, int y, char color); + int countValidMoves(); + void display(); + +public: + void flip(int x, int y); + int countFlipsInDirection(int x, int y, int dx, int dy, char color); + void resetValidMovesGrid(); + void prepareValidMovesGrid(char color); + void updateDisplayGrid(); + void putValidMoves(); +}; \ No newline at end of file diff --git a/Othello/codes/Structures/Bot.cpp b/Othello/codes/Structures/Bot.cpp new file mode 100644 index 0000000..5737ddf --- /dev/null +++ b/Othello/codes/Structures/Bot.cpp @@ -0,0 +1,372 @@ +#include "Bot.h" +#include "Board.h" +#include "Move.h" +#include "../Utilities/IntToStr.h" +#include "../Utilities/StrToInt.h" +#include +#include +#include + + + +Bot::Bot(std::string name, int difficulty) +{ + this -> name = name; + this -> difficulty = difficulty; +} + + +Move Bot::suggestMove(Board& board, char color) +// the fucntion called by GameMaster when it's bot's turn to move +// this function should be called when prepareBoardForMove is called +// so bot will work as expected +{ + if (difficulty == 1) // easy + { + return suggestEasyMove(board, color); + } + else if (difficulty == 2) // medium + { + return suggestMediumMove(board, color); + } + else if (difficulty == 3) // hard + { + return suggestHardMove(board, color); + } + else + { + return suggestMediumMove(board, color); // fallback + } +} + + + +Move Bot::suggestEasyMove(Board& board, char color) +// the decision a bot would make in easy mode +// the act is based on a random choice +{ + int validMovesCount = board.countValidMoves(); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution distrib(0, validMovesCount-1); + int randomNumber = distrib(gen); + + int moveNumber = 0; + for (int y = 0; y < board.BoardSize; y++) + { + for (int x = 0; x < board.BoardSize; x++) + { + if (board.validMovesGrid[y][x] == 'O') + { + if (moveNumber == randomNumber) + { + Move move{x, y, color}; + return move; + } + else + { + moveNumber++; + } + } + } + } +} + + + +Move Bot::suggestMediumMove(Board& board, char color) +// the decision a bot would make in medium mode +// the act is based on how much gain each move will have +// the move with the most gain is made +{ + int moveX, moveY; + int maxGain = 0; + + for (int y = 0; y < board.BoardSize; y++) + { + for (int x = 0; x < board.BoardSize; x++) + { + if (board.validMovesGrid[y][x] == 'O') + { + int gain = board.isValid(x, y, color); + if (gain > maxGain) + { + moveY = y; + moveX = x; + maxGain = gain; + } + } + } + } + + Move move{moveX, moveY, color}; + return move; +} + + + +Move Bot::suggestHardMove(Board& board, char color) +// the decision a bot would make in hard mode +{ + char oppColor = (color == 'W' ? 'B':'W'); + int index = 0; + int validMovesCount = board.countValidMoves(); + + Move moves[validMovesCount]; + + for (int y = 0; y < board.BoardSize; y++) + { + for (int x = 0; x < board.BoardSize; x++) + { + if (board.validMovesGrid[y][x] == 'O') + { + Move move {x, y, color}; + moves[index] = move; + index++; + } + } + } + + Move bestMove = moves[0]; + int bestScore = INT_MIN; + for (int i = 0; i < validMovesCount; i++) + { + Move move = moves[i]; + + Board dummyBoard {board.BoardSize}; + + deepCopy(board, dummyBoard); + + dummyBoard.putPiece(move.x, move.y, move.color); + dummyBoard.prepareBoardForMove(oppColor); + + int score = minimax(oppColor, dummyBoard, false, color); // start minimizing since its user's turn + + if (score > bestScore) + { + bestScore = score; + bestMove = move; + } + } + + return bestMove; +} + + + +std::string Bot::retrieveBot() +// turns Bot structure into a string to be saved inside database +// this string will be loaded using loadBot method +{ + return name + "|" + intToStr(difficulty); +} + + + +void Bot::loadBot(std::string bot) +// fills Bot's properties according to the given string +// this string is bot's informating saved inside database +// this string was made using retrieveBot method +{ + int seperatorIndex = bot.find("|"); + name = bot.substr(0, seperatorIndex); + difficulty = strToInt(bot.substr(seperatorIndex + 1, bot.size())); +} + + +//----------------------------------------- minimax algorithm ---------------------------------------------------- +int Bot::ratePlacement(int x, int y, int boardSize) +// Board is passed by refrence for time optimization +// if we put a piece with these information, what score would the piece get +// for the coordinates itselves +{ + // corners + if ((x == boardSize - 1 || x == 0) && (y == boardSize - 1 || y == 0)) + return 100; + + // leading to corner moves: + else if ( + (x == boardSize - 2 && y == 1) || + (x == boardSize - 2 && y == boardSize - 2) || + (x == 1 && y == 1) || + (x == 1 && y == boardSize - 2) + ) + return -50; + else if ( + (x == boardSize - 2 && y == boardSize - 1) || + (x == boardSize - 2 && y == 0) || + (x == 1 && y == 0) || + (x == 1 && y == boardSize - 1) || + (x == boardSize - 1 && y == 1) || + (x == boardSize - 1 && y == boardSize - 2) || + (x == 0 && y == 1) || + (x == 0 && y == boardSize - 2) + ) + return -30; + + // edges (corners and corner neighbour already handled) + else if (x == 0 || x == boardSize - 1 || y == 0 || y == boardSize - 1) + return 3; + + // center + else if ( + (x == boardSize/2 && y == boardSize/2) || + (x == boardSize/2 && y == boardSize/2 - 1) || + (x == boardSize/2 - 1 && y == boardSize/2) || + (x == boardSize/2 - 1 && y == boardSize/2 - 1) + ) + return 3; + + // other places have no value on their own + return 0; +} + + +int Bot::rateBoard(Board& board, char botColor) +{ + char oppColor = (botColor == 'W' ? 'B':'W'); + + Board dummyBoard {board.BoardSize}; + deepCopy(board, dummyBoard); + + int botScore = 0, playerScore = 0; + + // possible moves rating + dummyBoard.prepareBoardForMove(botColor); + botScore += dummyBoard.countValidMoves(); + + dummyBoard.prepareBoardForMove(oppColor); + botScore -= dummyBoard.countValidMoves(); + + // greedy strategy + int blackCount = dummyBoard.countBlack(); + int whiteCount = dummyBoard.countWhite(); + + if (botColor == 'B') + { + botScore += blackCount; + botScore -= whiteCount; + } + else + { + botScore -= blackCount; + botScore += whiteCount; + } + + // placement score + for (int y = 0; y < board.BoardSize; y++) + { + for (int x =0; x < board.BoardSize; x++) + { + char element = board.grid[y][x]; + + if (element == 'W' || element == 'B') + { + int score = ratePlacement(x, y, board.BoardSize); + + if (element == botColor) + { + botScore += score; + } + else + { + botScore -= score; + } + } + } + } + + return botScore; +} + + +int Bot::minimax(char color, Board dummyBoard, bool isMaxAgent, char botColor, int depth, int upFloor, int downFloor) +{ + int index = 0; + int validMovesCount = dummyBoard.countValidMoves(); + + if (depth == 3 || validMovesCount == 0) + { + return rateBoard(dummyBoard, botColor); + } + + Move moves[validMovesCount]; + + for (int y = 0; y < dummyBoard.BoardSize; y++) + { + for (int x = 0; x < dummyBoard.BoardSize; x++) + { + if (dummyBoard.validMovesGrid[y][x] == 'O') + { + Move move {x, y, color}; + moves[index] = move; + index++; + } + } + } + + if (isMaxAgent) // maximizing + { + int curMax = INT_MIN; + + // for each valid move in this turn + for (int i = 0; i < validMovesCount; i++) + { + Move move = moves[i]; + + Board newBoard{dummyBoard.BoardSize}; + + deepCopy(dummyBoard, newBoard); + newBoard.putPiece(move.x, move.y, move.color); + newBoard.prepareBoardForMove(move.color == 'B' ? 'W':'B'); + + int minAgentAnswer = minimax((move.color == 'B' ? 'W':'B'), newBoard, false, botColor, depth + 1, upFloor, downFloor); + + curMax = std::max(curMax, minAgentAnswer); + upFloor = std::max(upFloor, curMax); + + if (upFloor >= downFloor) break; + } + + return curMax; + } + + else // minimizing + { + int curMin = INT_MAX; + + // for each valid move in this turn + for (int i = 0; i < validMovesCount; i++) + { + Move move = moves[i]; + + Board newBoard{dummyBoard.BoardSize}; + + deepCopy(dummyBoard, newBoard); + newBoard.putPiece(move.x, move.y, move.color); + newBoard.prepareBoardForMove(move.color == 'B' ? 'W':'B'); + + int maxAgentAnswer = minimax((move.color == 'B' ? 'W':'B'), newBoard, true, botColor, depth + 1, upFloor, downFloor); + + curMin = std::min(curMin, maxAgentAnswer); + downFloor = std::min(downFloor, curMin); + + if (upFloor >= downFloor) break; + } + + return curMin; + } +} + + +void Bot::deepCopy(Board& board, Board& newBoard) +{ + for (int y = 0; y < board.BoardSize; y++) + { + for (int x = 0; x < board.BoardSize; x++) + { + newBoard.grid[y][x] = board.grid[y][x]; + } + } +} \ No newline at end of file diff --git a/Othello/codes/Structures/Bot.h b/Othello/codes/Structures/Bot.h new file mode 100644 index 0000000..aae85b0 --- /dev/null +++ b/Othello/codes/Structures/Bot.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include "Board.h" +#include "Move.h" + +struct Bot +{ + std::string name; + int difficulty; + + public: + Bot(std::string name, int difficulty); + Move suggestMove(Board& board, char color); + std::string retrieveBot(); + void loadBot(std::string bot); + + private: + Move suggestEasyMove(Board& borad, char color); + Move suggestMediumMove(Board& board, char color); + Move suggestHardMove(Board& board, char color); + int ratePlacement(int x, int y, int boardSize); + int rateBoard(Board& board, char botColor); + int minimax(char color, Board dummyBoard, bool isMaxAgent, char botColor, int depth = 0, int upFloor = INT_MIN, int downFloor = INT_MAX); + void deepCopy(Board& board, Board& newBoard); +}; \ No newline at end of file diff --git a/Othello/codes/Structures/Config.cpp b/Othello/codes/Structures/Config.cpp new file mode 100644 index 0000000..c429c14 --- /dev/null +++ b/Othello/codes/Structures/Config.cpp @@ -0,0 +1,58 @@ +#include "../Database/ConfigManager.h" +#include "Config.h" +#include +#include + + +Config* Config::instance = nullptr; + +Config::Config() +{ + try + { + GAME_HISTORY_LIMIT = getGameHistoryLimit(); + BOARD_SIZE = getBoardSize(); + BOARD_COLOR = getBoardColor(); + BOARD_BORDER_COLOR = getBoardBorderColor(); + SOUND = getSound(); + SHOW_AVAILABLE_PLACES_FOR_PIECES = getShowAvailablePlacesForPieces(); + HINT = getHint(); + HINT_LIMIT = getHintLimit(); + } + catch (int errorCode) + { + if (errorCode == 1) + { + system("cls"); + std::cout << "An error acoured while opening Config.txt \nPerhaps another program is using it\nPress any key to continue"; + getchar(); + } + else if (errorCode == 2) + { + system("cls"); + std::cout << "the inputed index is out of the bond of Config, the last index is 7\nPress any key to continue"; + getchar(); + } + else + { + system("cls"); + std::cout << "Unexpected error acoured\nPress any key to continue"; + getchar(); + } + } +} + + +Config* Config::getInstance() +{ + if (!instance) + throw 3; // config not initialized + return instance; +} + + +void Config::initialize() +{ + if (!instance) + instance = new Config(); +} \ No newline at end of file diff --git a/Othello/codes/Structures/Config.h b/Othello/codes/Structures/Config.h new file mode 100644 index 0000000..47b0225 --- /dev/null +++ b/Othello/codes/Structures/Config.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +struct Config +{ + static Config* instance; + + int GAME_HISTORY_LIMIT; + int BOARD_SIZE; + std::string BOARD_COLOR; + std::string BOARD_BORDER_COLOR; + bool SOUND; + bool SHOW_AVAILABLE_PLACES_FOR_PIECES; + bool HINT; + int HINT_LIMIT; + +private: + Config(); + +public: + static Config* getInstance(); + static void initialize(); +}; \ No newline at end of file diff --git a/Othello/codes/Structures/Move.cpp b/Othello/codes/Structures/Move.cpp new file mode 100644 index 0000000..49aee94 --- /dev/null +++ b/Othello/codes/Structures/Move.cpp @@ -0,0 +1,44 @@ +#include "Move.h" +#include "../Utilities/StrToInt.h" +#include "../Utilities/IntToStr.h" +#include "../Utilities/StrToSymbol.h" +#include "../Utilities/SymbolToStr.h" +#include + + +Move::Move() +{ + this -> x = 0; + this -> y = 0; + this -> color = 'B'; +} + + +Move::Move(int x, int y, char color) +{ + this -> x = x; + this -> y = y; + this -> color = color; +} + +std::string Move::retrieveMove() +{ + return intToStr(x) + " " + intToStr(y) + " " + symbolToStr(color); +} + +void Move::loadMove(std::string move) +{ + int xLastIndex = move.find(" "); + std::string x = move.substr(0, xLastIndex); + move = move.substr(xLastIndex + 1, move.size()); + + int yLastIndex = move.find(" "); + std::string y = move.substr(0, yLastIndex); + move = move.substr(yLastIndex + 1, move.size()); + + std::string color = move.substr(0, move.size()); + + this -> x = strToInt(x); + this -> y = strToInt(y); + this -> color = strToSymbol(color); +}; diff --git a/Othello/codes/Structures/Move.h b/Othello/codes/Structures/Move.h new file mode 100644 index 0000000..e768ebd --- /dev/null +++ b/Othello/codes/Structures/Move.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +struct Move +{ + int x; + int y; + char color; + +public: + Move(int x, int y, char color); + Move(); + std::string retrieveMove(); + void loadMove(std::string move); +}; \ No newline at end of file diff --git a/Othello/codes/Structures/MultiPlayerGame.cpp b/Othello/codes/Structures/MultiPlayerGame.cpp new file mode 100644 index 0000000..f3a8171 --- /dev/null +++ b/Othello/codes/Structures/MultiPlayerGame.cpp @@ -0,0 +1,190 @@ +#include "../Database/GameHistoryManager.h" +#include "../Database/GameLogManager.h" +#include "../Utilities/IntToStr.h" +#include "../Utilities/SymbolToStr.h" +#include "Board.h" +#include "Player.h" +#include "MultiPlayerGame.h" +#include +#include +#include + + +MultiPlayerGame::MultiPlayerGame(Board* GameBoard, Player* Player1, Player* Player2, char CurrentTurnColor) +{ + int lastId = getLastGameId(); + + this -> id = lastId + 1; + this -> GameBoard = GameBoard; + this -> mode = "2Player"; + this -> Player1 = Player1; + this -> Player2 = Player2; + this -> CurrentTurnColor = CurrentTurnColor; + this -> Winner = 0; +} + + +void MultiPlayerGame::changeTurn() +{ + if (CurrentTurnColor == 'B') + { + CurrentTurnColor = 'W'; + } + else + { + CurrentTurnColor = 'B'; + } +} + + +void MultiPlayerGame::start() +{ + clearGameLog(); + (*GameBoard).prepareBoardForMove(CurrentTurnColor); + + this -> save(); + + while (true) + { + system("cls"); + std::cout << (CurrentTurnColor == Player1 -> color ? Player2 -> name + "it's your turn" : Player2 -> name + "it's your turn") << std::endl; + + (*GameBoard).display(); + + int userInput = getch(); + + if (userInput == static_cast('w') || userInput == 72) + { + if (GameBoard -> CursorY != 0) + { + GameBoard -> CursorY--; + } + } + + else if (userInput == static_cast('s') || userInput == 80) + { + if (GameBoard -> CursorY != GameBoard -> BoardSize - 1) + { + GameBoard -> CursorY++; + } + } + + else if (userInput == static_cast('a') || userInput == 75) + { + if (GameBoard -> CursorX != 0) + { + GameBoard -> CursorX--; + } + } + + else if (userInput == static_cast('d') || userInput == 77) + { + if (GameBoard -> CursorX != GameBoard -> BoardSize - 1) + { + GameBoard -> CursorX++; + } + } + + else if (userInput == 13) + { + if ( (*GameBoard).validMovesGrid[GameBoard -> CursorY][GameBoard -> CursorX] == 'O') + { + (*GameBoard).putPiece(GameBoard -> CursorX, GameBoard -> CursorY, CurrentTurnColor); + changeTurn(); + (*GameBoard).prepareBoardForMove(CurrentTurnColor); + this -> save(); + + if ((*GameBoard).countValidMoves() == 0) + { + changeTurn(); + (*GameBoard).prepareBoardForMove(CurrentTurnColor); + this -> save(); + + if ( (*GameBoard).countValidMoves() == 0) + { + end(); + return; + } + } + } + } + } +} + + +void MultiPlayerGame::end() +{ + int blackCount = (*GameBoard).countBlack(); + int whiteCount = (*GameBoard).countWhite(); + + system("cls"); + (*GameBoard).display(); + + std::cout << "\nFinal Score: Black: " << blackCount + << " | White: " << whiteCount << "\n\n"; + + if (blackCount > whiteCount) + { + + if (Player1->color == 'B') + { + Winner = 1; + std::cout << "Congratulations " << Player1->name << "! You won!\n"; + } + else + { + Winner = 2; + std::cout << "Congratulations " << Player2->name << "! You won!\n"; + } + } + + else if (whiteCount > blackCount) + { + if (Player1->color == 'W') + { + Winner = 1; + std::cout << "Congratulations " << Player1->name << "! You won!\n"; + } + else + { + Winner = 2; + std::cout << "You lost. The bot won.\n"; + } + } + + else + { + Winner = 3; // Draw + std::cout << "It's a draw!\n"; + } + + this->save(); +} + + +void MultiPlayerGame::save() +// saves the game inside GameHistory.txt +{ + saveGame(this); +} + + +std::string MultiPlayerGame::retrieveGame() +// turns current Game instance into a string +// this string can be turned into a Game instance via loadGame method +{ + std::string idToSave = intToStr(id); + std::string boardToSave = (*GameBoard).retrieveBoard(); + std::string player1ToSave = Player1 -> retrievePlayer(); + std::string player2ToSave = Player2 -> retrievePlayer(); + std::string currentTurnToSave = symbolToStr(CurrentTurnColor); + std::string winnerToSave = intToStr(Winner); + + return "[" + idToSave + "][" + + boardToSave + "][" + + mode + "][" + + player1ToSave + "][" + + player2ToSave + "][" + + currentTurnToSave + "][" + + winnerToSave + "]"; +} diff --git a/Othello/codes/Structures/MultiPlayerGame.h b/Othello/codes/Structures/MultiPlayerGame.h new file mode 100644 index 0000000..9a72a43 --- /dev/null +++ b/Othello/codes/Structures/MultiPlayerGame.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Board.h" +#include "Player.h" +#include + +struct MultiPlayerGame +{ + int id; + Board* GameBoard; + std::string mode; + Player* Player1; + Player* Player2; + char CurrentTurnColor; + int Winner; + +public: + MultiPlayerGame(Board* GameBoard, Player* Player1, Player* Player2, char CurrentTurnColor); + void start(); + std::string retrieveGame(); + +private: + void end(); + void save(); + void changeTurn(); +}; diff --git a/Othello/codes/Structures/Player.cpp b/Othello/codes/Structures/Player.cpp new file mode 100644 index 0000000..1df0c5b --- /dev/null +++ b/Othello/codes/Structures/Player.cpp @@ -0,0 +1,27 @@ +#include +#include "Player.h" +#include "../Utilities/SymbolToStr.h" +#include "../Utilities/StrToSymbol.h" + + +Player::Player(std::string name, char color) +{ + this -> name = name; + this -> color = color; +} + +std::string Player::retrievePlayer() +{ + return name + "|" + symbolToStr(color); +} + +void Player::loadPlayer(std::string player) +{ + int speratorIndex = player.find("|"); + + std::string name = player.substr(0, speratorIndex); + std::string color = player.substr(speratorIndex + 1, player.size()); + + this -> name = name; + this -> color = strToSymbol(color); +} diff --git a/Othello/codes/Structures/Player.h b/Othello/codes/Structures/Player.h new file mode 100644 index 0000000..b6b8f51 --- /dev/null +++ b/Othello/codes/Structures/Player.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +struct Player +{ + std::string name; + char color; + +public: + Player(std::string name, char color); + std::string retrievePlayer(); + void loadPlayer(std::string player); +}; \ No newline at end of file diff --git a/Othello/codes/Structures/SinglePlayerGame.cpp b/Othello/codes/Structures/SinglePlayerGame.cpp new file mode 100644 index 0000000..06dcd3d --- /dev/null +++ b/Othello/codes/Structures/SinglePlayerGame.cpp @@ -0,0 +1,213 @@ +#include "../Database/GameHistoryManager.h" +#include "../Database/GameLogManager.h" +#include "../Utilities/IntToStr.h" +#include "../Utilities/SymbolToStr.h" +#include "Board.h" +#include "Bot.h" +#include "Player.h" +#include "SinglePlayerGame.h" +#include +#include +#include + + +SinglePlayerGame::SinglePlayerGame(Board *GameBoard, Player* Player1, Bot* GameBot, char CurrentTurnColor) +{ + int lastId = getLastGameId(); + + this -> id = lastId + 1; + this -> GameBoard = GameBoard; + this -> mode = "1Player"; + this -> Player1 = Player1; + this -> GameBot = GameBot; + this -> CurrentTurnColor = CurrentTurnColor; + this -> Winner = 0; +} + + +void SinglePlayerGame::changeTurn() +{ + if (CurrentTurnColor == 'B') + { + CurrentTurnColor = 'W'; + } + else + { + CurrentTurnColor = 'B'; + } +} + + +void SinglePlayerGame::start() +{ + clearGameLog(); + (*GameBoard).prepareBoardForMove(CurrentTurnColor); + + this -> save(); + + while (true) + { + if (CurrentTurnColor != Player1 -> color) // Bot turn + { + Move botMove = GameBot -> suggestMove( (*GameBoard), CurrentTurnColor); + GameBoard -> putPiece(botMove.x, botMove.y, CurrentTurnColor); + + changeTurn(); + (*GameBoard).prepareBoardForMove(CurrentTurnColor); + this -> save(); + + if ( (*GameBoard).countValidMoves() == 0) + { + changeTurn(); + (*GameBoard).prepareBoardForMove(CurrentTurnColor); + this -> save(); + + if ( (*GameBoard).countValidMoves() == 0) + { + end(); + return; + } + } + } + + system("cls"); + + (*GameBoard).display(); + + int userInput = getch(); + + if (userInput == static_cast('w') || userInput == 72) + { + if (GameBoard -> CursorY != 0) + { + GameBoard -> CursorY--; + } + } + + else if (userInput == static_cast('s') || userInput == 80) + { + if (GameBoard -> CursorY != GameBoard -> BoardSize - 1) + { + GameBoard -> CursorY++; + } + } + + else if (userInput == static_cast('a') || userInput == 75) + { + if (GameBoard -> CursorX != 0) + { + GameBoard -> CursorX--; + } + } + + else if (userInput == static_cast('d') || userInput == 77) + { + if (GameBoard -> CursorX != GameBoard -> BoardSize - 1) + { + GameBoard -> CursorX++; + } + } + + else if (userInput == 13) + { + if ( (*GameBoard).validMovesGrid[GameBoard -> CursorY][GameBoard -> CursorX] == 'O') + { + (*GameBoard).putPiece(GameBoard -> CursorX, GameBoard -> CursorY, CurrentTurnColor); + changeTurn(); + (*GameBoard).prepareBoardForMove(CurrentTurnColor); + this -> save(); + + if ((*GameBoard).countValidMoves() == 0) + { + changeTurn(); + (*GameBoard).prepareBoardForMove(CurrentTurnColor); + this -> save(); + + if ( (*GameBoard).countValidMoves() == 0) + { + end(); + return; + } + } + } + } + } +} + + +void SinglePlayerGame::end() +{ + int blackCount = (*GameBoard).countBlack(); + int whiteCount = (*GameBoard).countWhite(); + + system("cls"); + (*GameBoard).display(); + + std::cout << "\nFinal Score: Black: " << blackCount + << " | White: " << whiteCount << "\n\n"; + + if (blackCount > whiteCount) + { + + if (Player1->color == 'B') + { + Winner = 1; + std::cout << "Congratulations " << Player1->name << "! You won!\n"; + } + else + { + Winner = 2; + std::cout << "You lost. The bot won.\n"; + } + } + + else if (whiteCount > blackCount) + { + if (Player1->color == 'W') + { + Winner = 1; + std::cout << "Congratulations " << Player1->name << "! You won!\n"; + } + else + { + Winner = 2; + std::cout << "You lost. The bot won.\n"; + } + } + + else + { + Winner = 3; // Draw + std::cout << "It's a draw!\n"; + } + + this->save(); +} + + +void SinglePlayerGame::save() +// saves the game inside GameHistory.txt +{ + saveGame(this); +} + + +std::string SinglePlayerGame::retrieveGame() +// turns current Game instance into a string +// this string can be turned into a Game instance via loadGame method +{ + std::string idToSave = intToStr(id); + std::string boardToSave = (*GameBoard).retrieveBoard(); + std::string playerToSave = Player1 -> retrievePlayer(); + std::string botToSave = GameBot -> retrieveBot(); + std::string currentTurnToSave = symbolToStr(CurrentTurnColor); + std::string winnerToSave = intToStr(Winner); + + return "[" + idToSave + "][" + + boardToSave + "][" + + mode + "][" + + playerToSave + "][" + + botToSave + "][" + + currentTurnToSave + "][" + + winnerToSave + "]"; +} \ No newline at end of file diff --git a/Othello/codes/Structures/SinglePlayerGame.h b/Othello/codes/Structures/SinglePlayerGame.h new file mode 100644 index 0000000..17f11be --- /dev/null +++ b/Othello/codes/Structures/SinglePlayerGame.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Board.h" +#include "Bot.h" +#include "Player.h" +#include + +struct SinglePlayerGame +{ + int id; + Board* GameBoard; + std::string mode; + Player* Player1; + Bot* GameBot; + char CurrentTurnColor; + int Winner; + +public: + SinglePlayerGame(Board* GameBoard, Player* Player1, Bot* GameBot, char CurrentTurnColor); + void start(); + std::string retrieveGame(); + +private: + void end(); + void save(); + void changeTurn(); +}; diff --git a/Othello/codes/Utilities/GetSavedBoardSize.cpp b/Othello/codes/Utilities/GetSavedBoardSize.cpp new file mode 100644 index 0000000..f30f7f9 --- /dev/null +++ b/Othello/codes/Utilities/GetSavedBoardSize.cpp @@ -0,0 +1,10 @@ +#include "../Database/GameHistoryManager.h" +#include "IntSqrt.h" +#include "GetSavedBoardSize.h" +#include + +int getSavedBoardSize(std::string board) +{ + int elementCount = getSavedBoardElementCount(board); + return intSqrt(elementCount); +} \ No newline at end of file diff --git a/Othello/codes/Utilities/GetSavedBoardSize.h b/Othello/codes/Utilities/GetSavedBoardSize.h new file mode 100644 index 0000000..c15cc16 --- /dev/null +++ b/Othello/codes/Utilities/GetSavedBoardSize.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +int getSavedBoardSize(std::string board); \ No newline at end of file diff --git a/Othello/codes/Utilities/IntSqrt.cpp b/Othello/codes/Utilities/IntSqrt.cpp new file mode 100644 index 0000000..ca5c3a0 --- /dev/null +++ b/Othello/codes/Utilities/IntSqrt.cpp @@ -0,0 +1,10 @@ +#include "IntSqrt.h" + +int intSqrt(int number) +{ + for (int i = 1; i < 100000000; i++) + { + if (i * i == number) return i; + } + return -1; +} \ No newline at end of file diff --git a/Othello/codes/Utilities/IntSqrt.h b/Othello/codes/Utilities/IntSqrt.h new file mode 100644 index 0000000..626ad82 --- /dev/null +++ b/Othello/codes/Utilities/IntSqrt.h @@ -0,0 +1,3 @@ +#pragma once + +int intSqrt(int); \ No newline at end of file diff --git a/Othello/codes/Utilities/IntToStr.cpp b/Othello/codes/Utilities/IntToStr.cpp new file mode 100644 index 0000000..7f54c04 --- /dev/null +++ b/Othello/codes/Utilities/IntToStr.cpp @@ -0,0 +1,73 @@ +#include +#include "IntToStr.h" + + + +char getChar(int digit); +int getDigitsCount(int number); + + + +std::string intToStr(int number) +{ + int numberLen = getDigitsCount(number); + + char result[numberLen]; + + for (int i = numberLen - 1; i >= 0; i--) + { + result[i] = getChar(number % 10); + number /= 10; + } + + std::string resultStr = ""; + + for (int i = 0; i < numberLen; i++) + { + resultStr += result[i]; + } + + return resultStr; +} + +char getChar(int digit) +{ + switch (digit) + { + case 0: + return '0'; + case 1: + return '1'; + case 2: + return '2'; + case 3: + return '3'; + case 4: + return '4'; + case 5: + return '5'; + case 6: + return '6'; + case 7: + return '7'; + case 8: + return '8'; + case 9: + return '9'; + } +} + +int getDigitsCount(int number) +{ + if (number == 0) return 1; + + int count = 0; + + while(number != 0) + { + number /= 10; + count++; + } + + return count; +} \ No newline at end of file diff --git a/Othello/codes/Utilities/IntToStr.h b/Othello/codes/Utilities/IntToStr.h new file mode 100644 index 0000000..db72168 --- /dev/null +++ b/Othello/codes/Utilities/IntToStr.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string intToStr(int); \ No newline at end of file diff --git a/Othello/codes/Utilities/Print.cpp b/Othello/codes/Utilities/Print.cpp new file mode 100644 index 0000000..d443a9f --- /dev/null +++ b/Othello/codes/Utilities/Print.cpp @@ -0,0 +1,7 @@ +#include +#include "Print.h" + +void print(std::string subject, int count) +{ + for (int i = 0; i < count; i++) std::cout << subject; +} \ No newline at end of file diff --git a/Othello/codes/Utilities/Print.h b/Othello/codes/Utilities/Print.h new file mode 100644 index 0000000..57f11c6 --- /dev/null +++ b/Othello/codes/Utilities/Print.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void print(std::string subject, int count); \ No newline at end of file diff --git a/Othello/codes/Utilities/StrToInt.cpp b/Othello/codes/Utilities/StrToInt.cpp new file mode 100644 index 0000000..6130301 --- /dev/null +++ b/Othello/codes/Utilities/StrToInt.cpp @@ -0,0 +1,51 @@ +#include +#include "StrToInt.h" + + + +int getNumber(char digit); + + + +// turns a string containing digits into a integer +int strToInt(std::string number) +{ + int result = 0; + + while (number != "") + { + result = result*10 + getNumber((char) number[0]); + number = number.substr(1, number.size()); + } + + return result; +} + +int getNumber(char digit) +{ + switch (digit) + { + case '0': + return 0; + case '1': + return 1; + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + default: + return 0; + } +} \ No newline at end of file diff --git a/Othello/codes/Utilities/StrToInt.h b/Othello/codes/Utilities/StrToInt.h new file mode 100644 index 0000000..4a9d929 --- /dev/null +++ b/Othello/codes/Utilities/StrToInt.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +int strToInt(std::string number); \ No newline at end of file diff --git a/Othello/codes/Utilities/StrToSymbol.cpp b/Othello/codes/Utilities/StrToSymbol.cpp new file mode 100644 index 0000000..9511b41 --- /dev/null +++ b/Othello/codes/Utilities/StrToSymbol.cpp @@ -0,0 +1,25 @@ +#include + +char strToSymbol(std::string character) +{ + if (character == "W") + { + return 'W'; + } + else if (character == "B") + { + return 'B'; + } + else if (character == ".") + { + return '.'; + } + else if (character == "X") + { + return 'X'; + } + else if (character == "O") + { + return 'O'; + } +} \ No newline at end of file diff --git a/Othello/codes/Utilities/StrToSymbol.h b/Othello/codes/Utilities/StrToSymbol.h new file mode 100644 index 0000000..142a6b1 --- /dev/null +++ b/Othello/codes/Utilities/StrToSymbol.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +char strToSymbol(std::string character); \ No newline at end of file diff --git a/Othello/codes/Utilities/SymbolToStr.cpp b/Othello/codes/Utilities/SymbolToStr.cpp new file mode 100644 index 0000000..7f95a82 --- /dev/null +++ b/Othello/codes/Utilities/SymbolToStr.cpp @@ -0,0 +1,25 @@ +#include + +std::string symbolToStr(char character) +{ + if (character == 'W') + { + return "W"; + } + else if (character == 'B') + { + return "B"; + } + else if (character == '.') + { + return "."; + } + else if (character == 'X') + { + return "X"; + } + else if (character == 'O') + { + return "O"; + } +} \ No newline at end of file diff --git a/Othello/codes/Utilities/SymbolToStr.h b/Othello/codes/Utilities/SymbolToStr.h new file mode 100644 index 0000000..2231312 --- /dev/null +++ b/Othello/codes/Utilities/SymbolToStr.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string symbolToStr(char); \ No newline at end of file diff --git a/Othello/codes/Utilities/outputDecoratedSavedGame.cpp b/Othello/codes/Utilities/outputDecoratedSavedGame.cpp new file mode 100644 index 0000000..8fbc0c4 --- /dev/null +++ b/Othello/codes/Utilities/outputDecoratedSavedGame.cpp @@ -0,0 +1,129 @@ +#include "../Database/GameHistoryManager.h" +#include "../Utilities/IntToStr.h" +#include "../Structures/Board.h" +#include "../Structures/Player.h" +#include "../Structures/Bot.h" +#include "../Structures/SinglePlayerGame.h" +#include "../Structures/MultiPlayerGame.h" +#include "GetSavedBoardSize.h" +#include "Print.h" +#include "outputDecoratedSavedGame.h" +#include + + + +void outputDecoratedSavedSinglePlayerGame(std::string savedGame); +void outputDecoratedSavedMultiPlayerGame(std::string savedGame); + + + +void outputDecoratedSavedGame(std::string savedGame) +{ + std::string gameMode = getProperty(2, savedGame); + + if (gameMode == "1Player") + { + return outputDecoratedSavedSinglePlayerGame(savedGame); + } + else + { + return outputDecoratedSavedMultiPlayerGame(savedGame); + } +} + + + +void outputDecoratedSavedSinglePlayerGame(std::string savedGame) +{ + int id = getGameId(savedGame); + Board board = *(getSinglePlayerGame(id).GameBoard); + Player player = *(getSinglePlayerGame(id).Player1); + Bot bot = *(getSinglePlayerGame(id).GameBot); + char CurrentTurnColor = getSinglePlayerGame(id).CurrentTurnColor; + int Winner = getSinglePlayerGame(id).Winner; + + SinglePlayerGame game {&board, &player, &bot, CurrentTurnColor}; + game.id = id; + game.Winner = Winner; + game.mode = "1Player"; + game.GameBoard -> CursorX = -1; + game.GameBoard -> CursorY = -1; + + + std::cout << "{\n id = " << intToStr(game.id) << "\n"; + + std::cout << " game board = { \n"; + game.GameBoard -> display(); + + std::cout << " mode = single player\n"; + + std::cout << " Player = {\n" + << " name = " << game.Player1 -> name << std::endl + << " color = " << (game.Player1 -> color == 'W' ? "White":"Black") << std::endl + << " }\n"; + + + int botDifficulty = game.GameBot -> difficulty; + std::string difficultyToOutput = ( + botDifficulty == 1 ? + "easy" : botDifficulty == 2 ? + "medium" : "hard" + ); + std::cout << " Bot = {\n" + << " difficulty = " << difficultyToOutput << std::endl + << " }\n"; + + int winner = game.Winner; + std::string winnerToOutput = ( + winner == 0 ? + "The game is unfinished" : winner == 1 ? + "Player has won this game" : winner == 2 ? + "Bot has won this game" : "The game resulted in a draw" + ); + std::cout << " " << winnerToOutput << "\n}\n"; +} + + + +void outputDecoratedSavedMultiPlayerGame(std::string savedGame) +{ + int id = getGameId(savedGame); + Board board = *(getMultiPlayerGame(id).GameBoard); + Player player1 = *(getMultiPlayerGame(id).Player1); + Player player2 = *(getMultiPlayerGame(id).Player2); + char CurrentTurnColor = getMultiPlayerGame(id).CurrentTurnColor; + int Winner = getMultiPlayerGame(id).Winner; + + MultiPlayerGame game {&board, &player1, &player2, CurrentTurnColor}; + game.id = id; + game.Winner = Winner; + game.mode = "2Player"; + game.GameBoard -> CursorX = -1; + game.GameBoard -> CursorY = -1; + + std::cout << "{\n id = " << intToStr(game.id) << "\n"; + + std::cout << " game board = { \n"; + game.GameBoard -> display(); + + std::cout << " mode = Multiplayer\n"; + + std::cout << " Player1 = {\n" + << " name = " << game.Player1 -> name << std::endl + << " color = " << (game.Player1 -> color == 'W' ? "White":"Black") << std::endl + << " }\n"; + + std::cout << " Player2 = {\n" + << " name = " << game.Player2 -> name << std::endl + << " color = " << (game.Player2 -> color == 'W' ? "White":"Black") << std::endl + << " }\n"; + + int winner = game.Winner; + std::string winnerToOutput = ( + winner == 0 ? + "The game is unfinished" : winner == 1 ? + "Player1 has won this game" : winner == 2 ? + "Player2 has won this game" : "The game resulted in a draw" + ); + std::cout << " " << winnerToOutput << "\n}\n"; +} \ No newline at end of file diff --git a/Othello/codes/Utilities/outputDecoratedSavedGame.h b/Othello/codes/Utilities/outputDecoratedSavedGame.h new file mode 100644 index 0000000..186c208 --- /dev/null +++ b/Othello/codes/Utilities/outputDecoratedSavedGame.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void outputDecoratedSavedGame(std::string savedGame); \ No newline at end of file diff --git a/Othello/myprogram.exe b/Othello/myprogram.exe new file mode 100644 index 0000000..7890890 Binary files /dev/null and b/Othello/myprogram.exe differ