diff --git a/README.md b/README.md
new file mode 100644
index 0000000..71e933d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,62 @@
+
+
+
+Hit & Blow
+CPSC 5042 Project
+
+This is a multi-player, client-server implementation of the Hit & Blow game (inspired by the [Clubhouse Games™ on Nintendo Switch](https://www.nintendo.com/sg/switch/as7t/index.html) and the board game [Mastermind](https://en.wikipedia.org/wiki/Mastermind_(board_game))).
+
+## Rules
+The player is assigned a random sequence of 4 colored pegs with the goal of guessing the correct position and color of each peg. After each guess, the player is told how many "hits" and "blows" they scored. A "hit" is when a peg has both correct color and correct position, and a "blow" is when the peg has correct color but incorrect position. To win, the player must guess the correct sequence within 8 attempts. The colors of the pegs are chosen from a set of six colors: red, yellow, blue, green, pink and white.
+
+Specific to this implementation, a server program will manage single and multi-player games. If a user (i.e. a client) chooses to play by themselves, the server will generate a random sequence of colored pegs for them to guess. If the user wants to play with someone else, then the server will match them with another user and generate a sequence of colored pegs. The two users take turns to guess the correct sequence within 8 combined attempts.
+
+## Instructions (for macOS)
+
+
+Compilation:
+
+Clone the repository locally. Build the source code in the terminal (opened at the root folder) by running the `make` command. Alternatively, run the following comands in the terminal
+
+
+g++ -std=c++11 -pthread -o server src/server/main.cpp src/server/RPCServer.cpp src/utility/HitAndBlow.cpp
+g++ -std=c++11 -o client src/client/main.cpp src/client/RPCClient.cpp src/utility/HitAndBlow.cpp
+
+
+
+
+
+Execution:
+
+Execute server and client programs:
+Method 1 -- Specify IP address and port number of the server (in the LAN). The server program and the client program can operate on two different computers on the same local area network (LAN). Replace `[PORT]` and `[IP_ADDRESS]` with the desired port in the server and its IP address.
+
+
+./server [PORT]
+./client [IP_ADDRESS] [PORT]
+
+
+Method 2 -- Use localhost (127.0.0.1) and port 8080 as default. When both server program and client program are placed on the same computer, simply execute the following commands to start the program.
+
+
+./server
+./client
+
+
+
+
+## Gameplay
+
+### Single-player mode
+1. Player selects single-player mode to start the game. (Server set the answer pegs.)
+2. Player types in four numbers which represent the colored pegs separated by a space to guess the color and position of four pegs.
+3. Server returns the "hits" and "blows" for the player's guess.
+4. Player could use this information to improve their guess.
+5. Repeat steps 2 - 4 until player guesses the correct answer (i.e. the player has won), or a total of 8 guess are made without getting the correct answer (i.e. the player has lost).
+
+### Multi-player mode
+1. This mode is essentially similar to the single-player mode. The key difference is that the server matches two players who select two-player mode to start the game.
+2. The two players take turns to guess the color and position of four pegs. The guess made by one player is relayed to the other player.
+3. They are allowed 4 guess each to estimate the correct peg sequence.
+
+
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..a46e751
--- /dev/null
+++ b/makefile
@@ -0,0 +1,8 @@
+CXX = g++
+CXXFLAGS = -std=c++11
+SERVER_NAME = server
+CLIENT_NAME = client
+
+hitandblow: src/server/main.cpp src/server/RPCServer.cpp src/utility/HitAndBlow.cpp src/client/main.cpp src/client/RPCClient.cpp src/utility/HitAndBlow.cpp
+ $(CXX) $(CXXFLAGS) -pthread -o $(SERVER_NAME) src/server/main.cpp src/server/RPCServer.cpp src/utility/HitAndBlow.cpp
+ $(CXX) $(CXXFLAGS) -o $(CLIENT_NAME) src/client/main.cpp src/client/RPCClient.cpp src/utility/HitAndBlow.cpp
\ No newline at end of file
diff --git a/media/README.md b/media/README.md
new file mode 100644
index 0000000..b814fa0
--- /dev/null
+++ b/media/README.md
@@ -0,0 +1,7 @@
+### Created using [ONLINE ASCII TOOLS](https://onlineasciitools.com/convert-text-to-ascii-art)
+
+Font Selection: 3-d
+
+Font Options:
+ - Horizontal Layout: Fitted
+ - Vertical Layout: Default
diff --git a/media/logo.docx b/media/logo.docx
new file mode 100644
index 0000000..f4ad769
Binary files /dev/null and b/media/logo.docx differ
diff --git a/media/logo.png b/media/logo.png
new file mode 100644
index 0000000..4af1797
Binary files /dev/null and b/media/logo.png differ
diff --git a/src/client/RPCClient.cpp b/src/client/RPCClient.cpp
new file mode 100644
index 0000000..ab8f6ce
--- /dev/null
+++ b/src/client/RPCClient.cpp
@@ -0,0 +1,301 @@
+#include "RPCClient.h"
+
+#define DELIMITER ";"
+#define SUCCESS "0"
+#define FAIL "-1"
+// --- RPC list ---
+#define RPC_CONNECT 1
+#define RPC_DISCONNECT 2
+#define RPC_SELECTMODE 3
+#define RPC_GUESS 4
+#define RPC_ISMYTURN 5
+#define RPC_ENDGAME 6
+// ----------------
+
+/**
+ * Creates a socket and connects the socket to the server
+ * @param ipAddress server ip address
+ * @param port port
+ * @throw error message if error occurs
+ */
+void RPCClient::connectToServer(string ipAddress, int port){
+
+ int domain = AF_INET;
+ int type = SOCK_STREAM;
+ int protocol = 0;
+ struct sockaddr_in serv_addr;
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_port = htons(port);
+
+ // Create a socket
+ if((this->sock = socket(domain, type, protocol)) < 0){
+ throw string("Failed to create a socket");
+ }
+
+ // Convert IPv4 and IPv6 addresses from text to binary form
+ if(inet_pton(AF_INET, ipAddress.c_str(), &serv_addr.sin_addr) <= 0){
+ throw string("Invalid address/ Address not supported");
+ }
+
+ // Connect the socket to the address of the server
+ if(connect(this->sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){
+ throw string("Failed to connect to the server");
+ }
+}
+
+/**
+ * Obtain username and password from user input, then call connectRPC for login to the server
+ */
+void RPCClient::loginToServer() {
+ cout << "\n********************************************\n";
+ cout << BOLDYELLOW << " LOGIN TO SERVER" << RESETTEXT << endl;
+ cout << "********************************************\n";
+ cout << " * username: 2-10 letters, contains only letters and digits\n";
+ cout << " * password: 4-10 letters, contains at least one letter, one digit,\n"
+ " and one special character(!@#$%^&)\n\n";
+ while(true){
+ // Ask user to enter username and password
+ string userName, password;
+ cout << FOREGRN << ">>>" << RESETTEXT << " USER NAME: ";
+ getline(cin, userName);
+ cout << FOREGRN << ">>>" << RESETTEXT << " PASSWORD: ";
+ getline(cin, password);
+ cout << endl;
+
+ // Login to the server program
+ if(true==connectRPC(userName, password)){
+ // login successful
+ break;
+ }
+ cout << "PLEASE RE-ENTER USERNAME AND PASSWORD\n" << endl;
+ }
+}
+
+/**
+ * Communicat with the server by sending and receiving a message via socket
+ * @param msg message to send to the server
+ * @param res response message from the server
+ * @throw error message if error occurs
+ */
+void RPCClient::talkToServer(string msg, string &res){
+
+ char buffer[1024] = {0};
+
+ // Send a message to the server
+ if((send(this->sock , msg.c_str(), sizeof (msg), 0) < 0)){
+ throw string("Failed to send a request to socket");
+ }
+
+ // Receive response from server
+ if((read(this->sock, buffer, sizeof(buffer)) < 0)){
+ throw string("Failed to read from socket");
+ }
+ res = buffer;
+}
+
+/**
+ * Connects to the server with username and password
+ * @param userName username
+ * @param password password
+ * @return true if login successful; false if login unsuccessful;
+ */
+bool RPCClient::connectRPC(string userName, string password){
+
+ string msg = to_string(RPC_CONNECT) + ";" + userName + ";" + password + ";";
+
+ // talk to server
+ string res;
+ talkToServer(msg, res);
+
+ // parse tokens
+ vector tokens;
+ parseTokens(const_cast(res.c_str()), tokens);
+
+ // Print the message from server
+ cout << FOREGRN << tokens[1] << RESETTEXT << endl << endl;
+
+ bool isSuccessful = false;
+ if(tokens[0] == SUCCESS){
+ isSuccessful = true;
+ }else if(tokens[0] == FAIL){
+ isSuccessful = false;
+ }
+ return isSuccessful;
+}
+
+/**
+ * Disconnect from the server
+ * @throw error message if error occurs
+ */
+void RPCClient::disconnectRPC(){
+ string msg = to_string(RPC_DISCONNECT);
+
+ // talk to server
+ string res;
+ talkToServer(msg, res);
+
+ // parse tokens
+ vector tokens;
+ parseTokens(const_cast(res.c_str()), tokens);
+
+ // Print the message from server
+ cout << FOREGRN << tokens[1] << RESETTEXT << endl << endl;
+
+ // close socket
+ if((close(sock)) < 0){
+ throw string("Failed to close the socket");
+ }
+}
+
+/**
+ * Send game mode to server
+ * When it is two-player mode, receive name of another player
+ * @param mode game mode to send to server
+ * @param anotherPlayer name of another player (only in weo-player mode)
+ */
+void RPCClient::selectModeRPC(int mode, string &anotherPlayer){
+
+ if(mode==MULTIPLAY){
+ cout << endl << "... FINDING ANOTHER PLAYER ..." << endl;
+ }
+ string msg = to_string(RPC_SELECTMODE) + ";" + to_string(mode);
+
+ // talk to server
+ string res;
+ talkToServer(msg, res);
+
+ // parse tokens
+ vector tokens;
+ parseTokens(const_cast(res.c_str()), tokens);
+
+ if(mode==MULTIPLAY){
+ if(tokens.size() == 2 && tokens[0] == SUCCESS){
+ cout << endl << FOREGRN << tokens[1] << " JOINED" << RESETTEXT << endl << endl;
+ anotherPlayer = tokens[1];
+ }
+ }
+}
+
+/**
+ * Send client's guess to server and receive number of hits and blows
+ * @param pegs number list of pegs
+ * @param hits number of hits
+ * @param blows number of blows
+ */
+void RPCClient::guessRPC(vector pegs, int &hits, int &blows) {
+ string peglist;
+ for(int p : pegs){
+ peglist += to_string(p) + " ";
+ }
+ string msg = to_string(RPC_GUESS) + ";" + peglist;
+
+ // talk to server
+ string res;
+ talkToServer(msg, res);
+
+ // parse tokens
+ vector tokens;
+ parseTokens(const_cast(res.c_str()), tokens);
+
+ if(tokens[0] == SUCCESS){
+ vector list = splitStr(tokens[1], " ");
+ hits = stoi(list[0]);
+ blows = stoi(list[1]);
+ }
+}
+
+/**
+ * Ask server whose turn it is (used only in two-player mode)
+ * If it is this player's turn: return true and receive result of previous guess by another player
+ * It it is another player's turn: return false and receive nothing
+ * @param prevGuess
+ * @param hits
+ * @param blows
+ * @return true if it is this player's turn; false if it is another player's turn
+ */
+bool RPCClient::isMyTurn(vector &prevGuess, int &hits, int &blows) {
+ string msg = to_string(RPC_ISMYTURN);
+
+ // talk to server
+ string res;
+ talkToServer(msg, res);
+
+ // parse tokens
+ vector tokens;
+ parseTokens(const_cast(res.c_str()), tokens);
+
+ if(tokens[0] == SUCCESS){ // YES
+ if(tokens.size() == 4){
+ vector list = splitStr(tokens[1], " ");
+ for(string s : list){
+ prevGuess.push_back(stoi(s));
+ }
+ hits = stoi(tokens[2]);
+ blows = stoi(tokens[3]);
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Ask server to end the game and receive the correct answer and the ending message from server
+ * @param answer
+ * @param endMsg
+ */
+void RPCClient::endGameRPC(vector &answer, vector &endMsg) {
+ string msg = to_string(RPC_ENDGAME);
+
+ // talk to server
+ string res;
+ talkToServer(msg, res);
+
+ // parse tokens
+ vector tokens;
+ parseTokens(const_cast(res.c_str()), tokens);
+
+ if(tokens[0] == SUCCESS){
+ vector list = splitStr(tokens[1], " ");
+ for(string s : list){
+ answer.push_back(stoi(s));
+ }
+ endMsg.push_back(tokens[2]);
+ endMsg.push_back(tokens[3]);
+ }
+}
+
+/**
+ * Populates a string vector with tokens extracted from the message
+ * @param buffer message(input)
+ * @param tokens tokens extracted from the buffer (output)
+*/
+void RPCClient::parseTokens(char *buffer, vector &tokens){
+ char* token;
+ char* rest = (char *) buffer;
+
+ while ((token = strtok_r(rest, DELIMITER, &rest))){
+ tokens.push_back(token);
+ }
+}
+
+/**
+ * Split a string by specified delimiter
+ * @param str
+ * @param delim
+ * @return a vector of strings
+ */
+vector RPCClient::splitStr(string str, string delim){
+ vector list;
+ size_t pos = 0;
+ while((pos = str.find(delim)) != string::npos){
+ list.push_back(str.substr(0, pos));
+ str.erase(0, pos + delim.length());
+ }
+ if(str.length()!=0){
+ list.push_back(str);
+ }
+ return list;
+}
+
+
+
diff --git a/src/client/RPCClient.h b/src/client/RPCClient.h
new file mode 100644
index 0000000..1575529
--- /dev/null
+++ b/src/client/RPCClient.h
@@ -0,0 +1,42 @@
+#ifndef HITANDBLOW_RPCCLIENT_H
+#define HITANDBLOW_RPCCLIENT_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "../utility/ConsoleColor.h"
+#define SINGLEPLAY 1
+#define MULTIPLAY 2
+
+using namespace std;
+
+/**
+ * RPCClient class is responsible for making remote procedure calls to the server
+ */
+class RPCClient {
+private:
+ int sock;
+
+ void parseTokens(char *buffer, vector &tokens);
+ vector splitStr(string str, string delim);
+
+public:
+ RPCClient(){}
+ virtual ~RPCClient(){}
+ void connectToServer(string ipAddress, int port);
+ void loginToServer();
+ void talkToServer(string msg, string &res);
+ bool connectRPC(string userName, string password);
+ void disconnectRPC();
+ void selectModeRPC(int mode, string &anotherPlayer);
+ void guessRPC(vector pegs, int& hits, int& blows);
+ bool isMyTurn(vector& prevGuess, int& hits, int& blows);
+ void endGameRPC(vector& answer, vector &endMsg);
+};
+
+
+#endif //HITANDBLOW_RPCCLIENT_H
diff --git a/src/client/main.cpp b/src/client/main.cpp
new file mode 100644
index 0000000..e4951bb
--- /dev/null
+++ b/src/client/main.cpp
@@ -0,0 +1,162 @@
+#include
+#include
+#include "RPCClient.h"
+#include "../utility/HitAndBlow.h"
+
+#define LOCALHOST "127.0.0.1"
+#define DEFAULT_PORT 8080
+
+/**
+ * Sleeps for specified seconds
+ * @param sec
+ */
+void sleep(int sec){
+ srand(time(0)); // Initialize random number generator with system clock
+ this_thread::sleep_for(chrono::seconds (sec) );
+}
+
+/**
+ * This is main function for client program.
+ * If the client program is called with server IP address and Port number,
+ * it connects to the server program at specified IP address and Port.
+ * If not, it uses localhost ip address and 8080 as default.
+ * @param argc number of arguments
+ * @param argv argv[0] program name
+ * argv[1] server ip address
+ * argv[2] port
+ */
+int main(int argc, char const* argv[]) {
+
+ try{
+ //---------------------------------
+ // Check Input Arguments
+ //---------------------------------
+ string ipAddress = LOCALHOST;
+ int port = DEFAULT_PORT;
+ if(argc == 3){
+ // use specified ip address and port
+ ipAddress = argv[1];
+ port = stoi(argv[2]);
+ }
+
+ //-----------------------
+ // Connect to the server
+ //-----------------------
+ RPCClient clientRPC;
+ clientRPC.connectToServer(ipAddress, port);
+
+ //-----------------------------
+ // Login to the server program
+ //-----------------------------
+ clientRPC.loginToServer();
+
+ //------------
+ // Start game
+ //------------
+ HitAndBlow game;
+ while(game.getGameMode() != ENDGAME){
+ game.gameHome();
+ vector ans;
+ vector endMsg;
+ vector guess;
+ string anotherPlayer;
+ int hits = 0;
+ int blows = 0;
+ switch (game.getGameMode()) {
+ case SINGLEPLAY: // Single-Player Mode
+ // RPC: SELECTMODE
+ clientRPC.selectModeRPC(SINGLEPLAY, anotherPlayer);
+
+ // guess until problem solved or all attempts are used
+ while(game.getIsSolved() == false && game.getAttempts().size() < 8){
+ guess.clear();
+ guess = game.guess();
+ // RPC: GUESS
+ clientRPC.guessRPC(guess, hits, blows);
+ if(hits == 4){
+ game.setIsSolved(true);
+ }
+ game.printHitsBlows(hits, blows);
+ }
+ // RPC: ENDGAME
+ clientRPC.endGameRPC(ans, endMsg);
+ game.setAnswer(ans);
+ game.endGame(endMsg);
+ break;
+
+ case MULTIPLAY: // Two-Player Mode
+ // RPC: SELECTMODE
+ clientRPC.selectModeRPC(MULTIPLAY, anotherPlayer);
+ game.addPlayer(0, anotherPlayer);
+
+ // guess until problem solved or all attempts are used
+ while(game.getIsSolved() == false && game.getAttempts().size() < 8) {
+
+ // RPC: ISMYTURN
+ guess.clear();
+ if(!clientRPC.isMyTurn(guess, hits, blows)){
+ // print the attempt no
+ game.printAttemptNo(game.getAttempts().size() + 1);
+ cout << "\n... " << anotherPlayer << " IS GUESSING ...\n\n";
+ // sleep for 5 sec before asking again
+ sleep(5);
+ }
+ while (!clientRPC.isMyTurn(guess, hits, blows)) {
+ // sleep for 5 sec before asking again
+ sleep(5);
+ }
+
+ // if there is previous guess, show the previous guess
+ if (guess.size() != 0) {
+ game.submitGuess(guess);
+ // print previous guess, and the number of hits & blows
+ //game.printAttemptNo(game.getAttempts().size());
+ cout << "\nGUESS BY " << anotherPlayer << ": ";
+ game.printPegs(guess);
+ cout << endl << endl;
+ game.printHitsBlows(hits, blows);
+ if(hits == 4){
+ game.setIsSolved(true);
+ }
+ }
+
+ // guess
+ if(game.getIsSolved() == false && game.getAttempts().size() < 8){
+ guess.clear();
+ guess = game.guess();
+ // RPC: GUESS
+ clientRPC.guessRPC(guess, hits, blows);
+ if(hits == 4){
+ game.setIsSolved(true);
+ }
+ game.printHitsBlows(hits, blows);
+ }
+ }
+ // RPC: ENDGAME
+ clientRPC.endGameRPC(ans, endMsg);
+ game.setAnswer(ans);
+ game.endGame(endMsg);
+ break;
+
+ case ENDGAME: // End the Game
+ game.setGameMode(ENDGAME);
+ break;
+ }
+ }
+ //----------------------------
+ // Disconnect from the server
+ //----------------------------
+ clientRPC.disconnectRPC();
+
+ }catch(string &msg){
+ cerr << msg << endl;
+ return -1;
+ }catch(invalid_argument & e){
+ cerr << "INVALID ARGUMENT ERROR:" << e.what() << endl;
+ return -1;
+ }catch(exception &e){
+ cerr << "ERROR:" << e.what() << endl;
+ return -1;
+ }
+ return 0;
+}
diff --git a/src/server/Client.h b/src/server/Client.h
new file mode 100644
index 0000000..755aca5
--- /dev/null
+++ b/src/server/Client.h
@@ -0,0 +1,41 @@
+#ifndef HITANDBLOW_CLIENT_H
+#define HITANDBLOW_CLIENT_H
+
+#include
+#include
+
+using namespace std;
+
+/**
+ * Client class is responsible for storing client information
+ */
+class Client {
+public:
+ int sock;
+ struct sockaddr_in cli_addr;
+private:
+ string username;
+ string password;
+
+public:
+ Client() {}
+ virtual ~Client() {}
+
+ const string &getUsername() const {
+ return username;
+ }
+
+ void setUsername(const string &username) {
+ Client::username = username;
+ }
+
+ const string &getPassword() const {
+ return password;
+ }
+
+ void setPassword(const string &password) {
+ Client::password = password;
+ }
+};
+
+#endif //HITANDBLOW_CLIENT_H
diff --git a/src/server/GameManager.h b/src/server/GameManager.h
new file mode 100644
index 0000000..f17adb3
--- /dev/null
+++ b/src/server/GameManager.h
@@ -0,0 +1,93 @@
+#ifndef HITANDBLOW_GAMEMANAGER_H
+#define HITANDBLOW_GAMEMANAGER_H
+
+#include "../utility/HitAndBlow.h"
+
+using namespace std;
+
+/**
+ * GameManager class is responsible for managing games that currently active.
+ * The shared resources are protected by mutex lock to avoid access collision by clients.
+ */
+class GameManager {
+private:
+ mutex mtx;
+public:
+ unordered_map activeGameList; //
+
+ /**
+ * Creat a new single play game and assign it to the specified client.
+ * @param clientNum
+ * @param clientName
+ */
+ void assignSinglePlayGame(int clientNum, string clientName){
+ mtx.lock(); // Lock mutex
+
+ HitAndBlow* game = new HitAndBlow(SINGLEPLAY);
+ game->addPlayer(clientNum, clientName);
+ activeGameList[clientNum] = game;
+
+ mtx.unlock(); // Unlock mutex
+ }
+
+ /**
+ * Find a two-player game (or create a new two-player game if not available)
+ * and assign it to the specified client.
+ * @param clientNum
+ * @param clientName
+ */
+ void assignMultiPlayGame(int clientNum, string clientName){
+ mtx.lock(); // Lock mutex
+
+ bool isAssigned = false;
+ for(auto &game : activeGameList){
+ // find an existing game to assign if available
+ if(game.second->getGameMode()==MULTIPLAY && game.second->getPlayers().size() < 2){
+ cout << "JOINED A TWO-PLAYER GAME\n";
+ game.second->addPlayer(clientNum, clientName);
+ activeGameList[clientNum] = game.second;
+ isAssigned = true;
+ break;
+ }
+ }
+ // create new game to assign if couldn't find existing game
+ if(!isAssigned){
+ // create new multiplayer game to assign
+ cout << "START A TWO-PLAYER GAME\n";
+ HitAndBlow* game = new HitAndBlow(MULTIPLAY);
+ set players;
+ players.insert(clientNum);
+ game->addPlayer(clientNum, clientName);
+ game->setNextPlayer(clientNum);
+ activeGameList[clientNum] = game;
+ }
+
+ mtx.unlock(); // Unlock mutex
+ }
+
+ /**
+ * Delete game object that was assigned to the specified client
+ * and remove it from the active game list.
+ * @param clientNum
+ */
+ void leaveGame(int clientNum){
+ mtx.lock(); // Lock mutex
+
+ if(activeGameList[clientNum]->getGameMode()==SINGLEPLAY){
+ delete activeGameList[clientNum]; // free memory
+ activeGameList[clientNum] = nullptr;
+ }else if(activeGameList[clientNum]->getGameMode()==MULTIPLAY){
+ activeGameList[clientNum]->removePlayer(clientNum);
+ // if there is no player
+ if(activeGameList[clientNum]->getPlayers().size() == 0){
+ delete activeGameList[clientNum]; // free memory
+ activeGameList[clientNum] = nullptr;
+ }
+ }
+ // remove from the active list
+ activeGameList.erase(clientNum);
+
+ mtx.unlock(); // Unlock mutex
+ }
+};
+#endif //HITANDBLOW_GAMEMANAGER_H
diff --git a/src/server/RPCServer.cpp b/src/server/RPCServer.cpp
new file mode 100644
index 0000000..5684ac9
--- /dev/null
+++ b/src/server/RPCServer.cpp
@@ -0,0 +1,364 @@
+#include "RPCServer.h"
+
+#define PORT 8080
+#define DELIMITER ";"
+// --- RPC list ---
+#define RPC_CONNECT 1
+#define RPC_DISCONNECT 2
+#define RPC_SELECTMODE 3
+#define RPC_GUESS 4
+#define RPC_ISMYTURN 5
+#define RPC_ENDGAME 6
+// ----------------
+#define SUCCESS "0"
+#define FAIL "-1"
+
+ /**
+ * Continue communicating with client until client disconnects
+ * @param cliNum No. of client
+ * @throw error message if error occurs
+ */
+void RPCServer::talkToClient(int cliNum){
+
+ bool continueLoop = true;
+ while(continueLoop){
+ int res;
+ char buffer[1024] = {0};
+ // receive the data on the socket and store it in a buffer
+ if ((res = recv(clients[cliNum].sock, buffer, sizeof(buffer), 0)) <= 0) {
+ if(res==0){
+ // res = 0 if the connection is already closed
+ cout << "... CLIENT " << to_string(cliNum) << " [Sock#" << to_string(clients[cliNum].sock) << "] DISCONNECTED ...\n" << endl;
+ // delete the child socket
+ close(clients[cliNum].sock);
+ break;
+ }else{
+ // res = -1 if unsuccessful
+ throw string("Failed to read from socket");
+ }
+ }else{
+ // parse tokens
+ vector tokens;
+ parseTokens(buffer, tokens);
+
+ // first token is the RPC specifier
+ string strRPC = tokens[0];
+ int rpcNum = stoi(strRPC);
+
+ // process RPC
+ switch (rpcNum) {
+ case RPC_CONNECT:
+ connectRPC(cliNum, tokens);
+ break;
+ case RPC_DISCONNECT:
+ disconnectRPC(cliNum);
+ continueLoop = false; // exit while loop
+ break;
+ case RPC_SELECTMODE:
+ selectModeRPC(cliNum, tokens);
+ break;
+ case RPC_GUESS:
+ guessRPC(cliNum, tokens);
+ break;
+ case RPC_ISMYTURN:
+ isMyTurnRPC(cliNum, tokens);
+ break;
+ case RPC_ENDGAME:
+ endGameRPC(cliNum);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Send a message to the client
+ * @param cliNum No. of client who requested this rpc
+ * @param msg message to send
+ * @throw error message if error occurs
+ */
+void RPCServer::sendToClient(int cliNum, string msg){
+
+ if((send(clients[cliNum].sock , msg.c_str() , msg.length() , 0 ) < 0)){
+ throw string("Failed to send the message to client");
+ }
+}
+
+/**
+ * [RPC] connectRPC: validate username and password for a client to login and send the response to client
+ * @param cliNum No. of client who requested this rpc
+ * @param tokens tokens received from the client
+ * @throw error message if error occurs
+ */
+void RPCServer::connectRPC(int cliNum, vector &tokens){
+
+ cout << "-------------------------------------------------" << endl;
+ cout << FOREGRN;
+ cout << "[CLIENT #" << cliNum << "]" << " LOGIN\n\n";
+
+ // regular expression for username (2 to 10 letters in "a-zA-Z0-9" )
+ regex regUsername ("[a-zA-Z0-9]{2,10}");
+ // regular expression for password (4-10 letters, contains at lease one letter, digit(0-9), special character (!@#$%^&))
+ regex regPassword ("^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$%^&]).{4,10}$");
+
+ if(tokens.size() < 3){
+ throw string("Invalid size of tokens");
+ }
+
+ // print the username and password received from client
+ cout << "USER NAME: " << tokens[1] << " PASSWORD: " << tokens[2] << endl;
+
+ // validate username and password
+ if(!regex_match(tokens[1], regUsername)){
+ string msg = (string)FAIL + ";LOGIN DENIED: INVALID USERNAME";
+ cout << "LOGIN DENIED: INVALID USERNAME" << endl;
+ // Send a message to the client
+ sendToClient(cliNum, msg);
+ }else if(!regex_match(tokens[2], regPassword)){
+ string msg = (string)FAIL + ";LOGIN DENIED: INVALID PASSWORD";
+ cout << "LOGIN DENIED: INVALID PASSWORD" << endl;
+ // Send a message to the client
+ sendToClient(cliNum, msg);
+ }else{
+ // username and password are valid
+ clients[cliNum].setUsername(tokens[1]);
+ clients[cliNum].setPassword(tokens[2]);
+ // Send a message to the client
+ string msg = (string)SUCCESS + ";[YOU ARE LOGGED IN] HELLO " + clients[cliNum].getUsername();
+ cout << "LOGIN SUCCESSFUL" << endl;
+ sendToClient(cliNum, msg);
+ }
+ cout << RESETTEXT;
+ cout << "-------------------------------------------------" << endl << endl;
+}
+
+/**
+ * [RPC] disconnectRPC: disconnect client from server and send the response to client
+ * @param cliNum No. of client who requested this rpc
+ */
+void RPCServer::disconnectRPC(int cliNum){
+
+ cout << "-------------------------------------------------" << endl;
+ cout << FOREGRN;
+ cout << "[CLIENT #" << cliNum << " : " << clients[cliNum].getUsername() << "]" << " DISCONNECT\n";
+
+ // Send response to the client
+ string msg = (string)SUCCESS + ";[YOU ARE LOGGED OUT] BYE " + clients[cliNum].getUsername();
+ sendToClient(cliNum, msg);
+
+ // delete the child socket
+ close(clients[cliNum].sock);
+
+ // remove client from lists
+ clients.erase(cliNum);
+
+ cout << RESETTEXT;
+ cout << "-------------------------------------------------" << endl << endl;
+}
+
+/**
+ * [RPC] selectModeRPC: set the selected game mode and send the response to client
+ * @param cliNum No. of client who requested this rpc
+ * @param tokens tokens received from the client
+ */
+void RPCServer::selectModeRPC(int cliNum, vector &tokens) {
+
+ cout << "-------------------------------------------------" << endl;
+ cout << FOREGRN;
+ cout << "[CLIENT #" << cliNum << " : " << clients[cliNum].getUsername() << "]" << " SELECT GAME MODE\n\n";
+
+ if(tokens.size() < 2){
+ throw string("Invalid size of tokens");
+ }
+ cout << "GAME MODE: " << stoi(tokens[1]) << endl;
+
+ string msg = (string)SUCCESS + ";";
+ if(stoi(tokens[1])==SINGLEPLAY){
+ // assign a single-player game to client
+ gameMgr.assignSinglePlayGame(cliNum, clients[cliNum].getUsername());
+ }else if(stoi(tokens[1])==MULTIPLAY){
+ // assign a two-player game to client
+ gameMgr.assignMultiPlayGame(cliNum, clients[cliNum].getUsername());
+
+ // wait until the second player to join
+ while(gameMgr.activeGameList[cliNum]->getPlayers().size() != 2){}
+
+ // set the response message
+ for(auto player : gameMgr.activeGameList[cliNum]->getPlayers()){
+ if(player.first != cliNum){
+ msg += player.second;
+ break;
+ }
+ }
+ }
+ // Send response to the client
+ sendToClient(cliNum, msg);
+
+ cout << RESETTEXT;
+ cout << "-------------------------------------------------" << endl << endl;
+}
+
+/**
+ * [RPC] guessRPC: set the guess for client and send the response to client
+ * @param cliNum No. of client who requested this rpc
+ * @param tokens tokens received from the client
+ */
+void RPCServer::guessRPC(int cliNum, vector &tokens) {
+
+ cout << "-------------------------------------------------" << endl;
+ cout << FOREGRN;
+ cout << "[CLIENT #" << cliNum << " : " << clients[cliNum].getUsername() << "]" << " GUESS PEGS\n\n";
+
+ if(tokens.size() < 2){
+ cout << "INVALID SIZE OF TOKENS";
+ throw string("Invalid size of tokens");
+ }
+ cout << "GUESS: " + tokens[1] << endl;
+
+ // submit client's guess
+ vector strs = splitStr(tokens[1], " ");
+ vector guess;
+ for(string str : strs){
+ guess.push_back(stoi(str));
+ }
+ gameMgr.activeGameList[cliNum]->submitGuess(guess);
+
+ // count hits and blows
+ int hits = 0;
+ int blows = 0;
+ gameMgr.activeGameList[cliNum]->countHitsBlows(guess, hits, blows);
+ cout << "HITS: " << to_string(hits) << " BLOWS: " << to_string(blows) << endl;
+
+ // if two-player mode, set the next player
+ if(gameMgr.activeGameList[cliNum]->getGameMode() == MULTIPLAY){
+ gameMgr.activeGameList[cliNum]->setNextPlayer(gameMgr.activeGameList[cliNum]->getAnotherPlayer(cliNum));
+ }
+
+ // Send response to client
+ string msg = (string)SUCCESS + ";" + to_string(hits) + " " + to_string(blows);
+ sendToClient(cliNum, msg);
+
+ cout << RESETTEXT;
+ cout << "-------------------------------------------------" << endl << endl;
+}
+
+/**
+ * [RPC] isMyTurnRPC: check whose turn is it next and send the response to client
+ * @param cliNum No. of client who requested this rpc
+ * @param tokens tokens received from the client
+ */
+void RPCServer::isMyTurnRPC(int cliNum, vector &tokens) {
+
+ cout << "-------------------------------------------------" << endl;
+ cout << FOREGRN;
+ cout << "[CLIENT #" << cliNum << " : " << clients[cliNum].getUsername() << "]" << " CHECKING WHOSE TURN\n\n";
+ string msg;
+
+ if(gameMgr.activeGameList[cliNum]->getNextPlayer() == cliNum){
+ cout << clients[cliNum].getUsername() << "'S TURN\n";
+ msg += (string)SUCCESS + ";";
+ if(gameMgr.activeGameList[cliNum]->getAttempts().size() != 0){
+ int hits = 0;
+ int blows = 0;
+ // add the result of previous attempt to msg
+ vector preAttempt = gameMgr.activeGameList[cliNum]->getAttempts().back();
+ gameMgr.activeGameList[cliNum]->countHitsBlows(preAttempt, hits, blows);
+ for(auto p : preAttempt){
+ msg += to_string(p) + " ";
+ }
+ msg += ";" + to_string(hits) + ";" + to_string(blows);
+ }
+ }else{
+ cout << clients[gameMgr.activeGameList[cliNum]->getAnotherPlayer(cliNum)].getUsername() << "'S TURN\n";
+ msg += (string)FAIL;
+ }
+
+ // Send response to the client
+ sendToClient(cliNum, msg);
+
+ cout << RESETTEXT;
+ cout << "-------------------------------------------------" << endl << endl;
+}
+
+/**
+ * [RPC] isMyTurnRPC: end the game for specified client and send the response to client
+ * @param cliNum No. of client who requested this rpc
+ */
+void RPCServer::endGameRPC(int cliNum) {
+
+ cout << "-------------------------------------------------" << endl;
+ cout << FOREGRN;
+ cout << "[CLIENT #" << cliNum << " : " << clients[cliNum].getUsername() << "]" << " END GAME\n";
+
+ string msg = (string)SUCCESS + ";";
+ for(int peg : gameMgr.activeGameList[cliNum]->getAnswer()){
+ msg += to_string(peg) + " ";
+ }
+
+ string players = clients[cliNum].getUsername();
+ if(gameMgr.activeGameList[cliNum]->getIsSolved()){
+ msg += ";CONGRATULATIONS!;";
+ msg += " GOOD JOB " + players;
+ }else{
+ msg += "; GAME OVER!;";
+ msg += "BETTER LUCK NEXT TIME " + players;
+ }
+ // Send response to client
+ sendToClient(cliNum, msg);
+ // Remove client from list
+ gameMgr.leaveGame(cliNum);
+
+ cout << RESETTEXT;
+ cout << "-------------------------------------------------" << endl << endl;
+}
+
+/**
+ * add client to the client list
+ * @param cliNum No. of client
+ * @param cliObj Client object
+ */
+void RPCServer::addClients(int cliNum, Client cliObj) {
+
+ mtx.lock(); // Lock mutex
+ clients[cliNum] = cliObj;
+ mtx.unlock(); // Unlock mutex
+}
+
+/**
+ * Populates a string vector with tokens extracted from the message the client sent
+ * @param buffer message the client sent (input)
+ * @param tokens tokens extracted from the buffer (output)
+ * Note: fist token is always a RPC specifier
+ * Example buffer: "1;username;password;"
+*/
+void RPCServer::parseTokens(char *buffer, vector &tokens){
+
+ char* token;
+ char* rest = (char *) buffer;
+
+ while ((token = strtok_r(rest, DELIMITER, &rest))){
+ tokens.push_back(token);
+ }
+}
+
+/**
+ * Split a string by specified delimiter
+ * @param str
+ * @param delim
+ * @return a vector of strings
+ */
+vector RPCServer::splitStr(string str, string delim){
+
+ vector list;
+ size_t pos = 0;
+ while((pos = str.find(delim)) != string::npos){
+ list.push_back(str.substr(0, pos));
+ str.erase(0, pos + delim.length());
+ }
+ if(str.length()!=0){
+ list.push_back(str);
+ }
+ return list;
+}
diff --git a/src/server/RPCServer.h b/src/server/RPCServer.h
new file mode 100644
index 0000000..800a528
--- /dev/null
+++ b/src/server/RPCServer.h
@@ -0,0 +1,47 @@
+#ifndef HITANDBLOW_RPCSERVER_H
+#define HITANDBLOW_RPCSERVER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "Client.h"
+#include "GameManager.h"
+
+using namespace std;
+
+/**
+ * RPCServer class is responsible for responding to the remote procedure calls from clients.
+ * The shared resources are protected by mutex lock to avoid access collision by clients.
+ */
+class RPCServer {
+
+private:
+ mutex mtx;
+ unordered_map clients; //
+ GameManager gameMgr;
+
+ void connectRPC(int cliNum, vector &tokens);
+ void disconnectRPC(int cliNum);
+ void selectModeRPC(int cliNum, vector &tokens);
+ void guessRPC(int cliNum, vector &tokens);
+ void isMyTurnRPC(int cliNum, vector &tokens);
+ void endGameRPC(int cliNum);
+ void sendToClient(int cliNum, string msg);
+ void parseTokens(char *buffer, vector &tokens);
+ vector splitStr(string str, string delim);
+
+public:
+ RPCServer(){}
+ virtual ~RPCServer(){}
+ void talkToClient(int cliNum);
+ void addClients(int cliNum, Client cliObj);
+};
+
+#endif //HITANDBLOW_RPCSERVER_H
diff --git a/src/server/main.cpp b/src/server/main.cpp
new file mode 100644
index 0000000..ae5689c
--- /dev/null
+++ b/src/server/main.cpp
@@ -0,0 +1,160 @@
+#include
+#include
+#include
+#include
+#include
+#include "RPCServer.h"
+#include "Client.h"
+#include "GameManager.h"
+
+#define PORT 8080
+
+//--- Global Variables --
+int cliNum = 0;
+RPCServer rpc;
+vector threads;
+mutex mtx;
+//-----------------------
+
+/**
+ * Creates a socket and binds to the server address
+ * @param port
+ * @param sock
+ * @throw error message if error occurs
+ */
+void createConnection(int port, int &sock){
+
+ int domain = AF_INET;
+ int type = SOCK_STREAM;
+ int protocol = 0;
+ struct sockaddr_in serv_addr;
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_addr.s_addr = INADDR_ANY;
+ serv_addr.sin_port = htons(port);
+
+ // Create a socket
+ if((sock = socket(domain, type, protocol))<0){
+ throw string("Failed to create a socket");
+ }
+ cout << "... CREATED SOCKET ...\n" << endl;
+
+ // Bind to an address
+ if (::bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){
+ throw string("Failed to bind to the address");
+ }
+ cout << "... BOUND TO ADDRESS ...\n" << endl;
+}
+
+/**
+ * Listens on the port for a client's connection to be established.
+ * @param sock
+ * @throw error message if error occurs
+ */
+void listenForClient(int sock){
+ // Listen on a port, and wait for a connection to be established
+ if (listen(sock, 3) < 0){
+ throw string("Failed to listen");
+ }
+ cout << "... LISTENING ...\n" << endl;
+}
+
+/**
+ * Accepts the connection from a client
+ * @param sock
+ * @throw error message if error occurs
+ */
+Client acceptConnection(int sock){
+ Client cliObj;
+ if((cliObj.sock = accept(sock, (struct sockaddr *)&cliObj.cli_addr,(socklen_t*)&cliObj.cli_addr)) < 0){
+ throw string("Failed to accept a connection from client");
+ }
+ cout << "... NEW CONNECTION ACCEPTED [Sock#" + to_string(cliObj.sock) + "] ...\n" << endl;
+ return cliObj;
+}
+
+/**
+ * Start interacting with client
+ * @param cliNum
+ * @return
+ */
+void startRPC(int cliNum){
+ cout << "... START THREAD FOR CLIENT #" << cliNum << " ..." << endl << endl;
+
+ // Start interacting with client
+ rpc.talkToClient(cliNum);
+
+ cout << "... END THREAD FOR CLIENT #" << cliNum << " ..." << endl << endl;
+}
+
+/**
+ * This is main function for server program.
+ * If the server program is called with Port number,
+ * it connects to the server program at specified IP address and Port.
+ * If not, it uses localhost ip address and 8080 as default.
+ * @param argc number of arguments
+ * @param argv argv[0] program name
+ * argv[1] port
+ */
+int main(int argc, char const* argv[]) {
+
+ try{
+ //---------------------------------
+ // Check Input Arguments
+ //---------------------------------
+ int port = PORT;
+ if(argc == 2){
+ // use specified port
+ port = stoi(argv[1]);
+ }
+
+ //---------------------------------
+ // Establish Connection for Server
+ //---------------------------------
+ int sock;
+ createConnection(port, sock);
+
+ //------------------------------------
+ // Listen to Clients & Create Threads
+ //------------------------------------
+ while(true){
+ try{
+ // Lock mutex
+ mtx.lock();
+
+ // Listening on a port
+ listenForClient(sock);
+
+ // Accept the connection from a client
+ Client newClient = acceptConnection(sock);
+
+ // create a thread for the new client (pass in clientNum)
+ thread newThread(startRPC, cliNum);
+
+ // store new client to rpc hashmap
+ rpc.addClients(cliNum, newClient);
+
+ // store new thread to vector
+ threads.push_back(move(newThread));
+
+ // increment client number
+ cliNum++;
+
+ // Unlock mutex
+ mtx.unlock();
+
+ }catch(string &msg){
+ cerr << msg << endl;
+ }catch(invalid_argument & e){
+ cerr << "INVALID ARGUMENT ERROR:" << e.what() << endl;
+ }catch(exception &e){
+ cerr << "ERROR:" << e.what() << endl;
+ }
+ }
+ }catch(std::invalid_argument & e){
+ cerr << "INVALID ARGUMENT ERROR:" << e.what() << endl;
+ return -1;
+ }catch(exception &e){
+ cerr << "ERROR:"<< e.what() << endl;
+ return -1;
+ }
+}
diff --git a/src/utility/ConsoleColor.h b/src/utility/ConsoleColor.h
new file mode 100644
index 0000000..504643b
--- /dev/null
+++ b/src/utility/ConsoleColor.h
@@ -0,0 +1,62 @@
+#ifndef HITANDBLOW_CONSOLECOLOR_H
+#define HITANDBLOW_CONSOLECOLOR_H
+
+/* FOREGROUND */
+//These codes set the actual text to the specified color
+#define RESETTEXT "\x1B[0m" //Set all colors back to normal.
+#define BOLDTEXT "\x1B[1m" //Bold.
+#define FOREBLK "\x1B[30m" //Black
+#define FORERED "\x1B[31m" //Red
+#define FOREGRN "\x1B[32m" //Green
+#define FOREYEL "\x1B[33m" //Yellow
+#define FOREBLU "\x1B[34m" //Blue
+#define FOREMAG "\x1B[35m" //Magenta
+#define FORECYN "\x1B[36m" //Cyan
+#define FOREWHT "\x1B[37m" //White
+
+#define BOLDBLACK "\x1B[1m\x1B[30m" //Bold Black
+#define BOLDRED "\x1B[1m\x1B[31m" //Bold Red
+#define BOLDGREEN "\x1B[1m\x1B[32m" //Bold Green
+#define BOLDYELLOW "\x1B[1m\x1B[33m" //Bold Yellow
+#define BOLDBLUE "\x1B[1m\x1B[34m" //Bold Blue
+#define BOLDMAGENTA "\x1B[1m\x1B[35m" //Bold Magenta
+#define BOLDCYAN "\x1B[1m\x1B[36m" //Bold Cyan
+#define BOLDWHITE "\x1B[1m\x1B[37m" //Bold White
+
+/* BACKGROUND */
+//These codes set the background color behind the text.
+#define BACKBLK "\x1B[40m"
+#define BACKRED "\x1B[41m"
+#define BACKGRN "\x1B[42m"
+#define BACKYEL "\x1B[43m"
+#define BACKBLU "\x1B[44m"
+#define BACKMAG "\x1B[45m"
+#define BACKCYN "\x1B[46m"
+#define BACKWHT "\x1B[47m"
+
+#define BLINK "\033[5m"
+#define FASTBLINK "\033[6m"
+
+//These will set the text color and then set it back to normal afterwards.
+#define BLK(x) FOREBLK x RESETTEXT
+#define RED(x) FORERED x RESETTEXT
+#define GRN(x) FOREGRN x RESETTEXT
+#define YEL(x) FOREYEL x RESETTEXT
+#define BLU(x) FOREBLU x RESETTEXT
+#define MAG(x) FOREMAG x RESETTEXT
+#define CYN(x) FORECYN x RESETTEXT
+#define WHT(x) FOREWHT x RESETTEXT
+//Example usage: cout << BLU("This text's color is now blue!") << endl;
+
+//These will set the text's background color then reset it back.
+#define BackBLK(x) BACKBLK x RESETTEXT
+#define BackRED(x) BACKRED x RESETTEXT
+#define BackGRN(x) BACKGRN x RESETTEXT
+#define BackYEL(x) BACKYEL x RESETTEXT
+#define BackBLU(x) BACKBLU x RESETTEXT
+#define BackMAG(x) BACKMAG x RESETTEXT
+#define BackCYN(x) BACKCYN x RESETTEXT
+#define BackWHT(x) BACKWHT x RESETTEXT
+//Example usage: cout << BACKRED(FOREBLU("I am blue text on a red background!")) << endl;
+
+#endif //HITANDBLOW_CONSOLECOLOR_H
diff --git a/src/utility/HitAndBlow.cpp b/src/utility/HitAndBlow.cpp
new file mode 100644
index 0000000..080e677
--- /dev/null
+++ b/src/utility/HitAndBlow.cpp
@@ -0,0 +1,431 @@
+#include "HitAndBlow.h"
+
+/**
+ * Constructor for HitAndBlow class
+ * @param gameMode
+ */
+HitAndBlow::HitAndBlow(){}
+
+/**
+ * Constructor for HitAndBlow class that takes gameMode as input
+ * @param gameMode
+ */
+HitAndBlow::HitAndBlow(int gameMode) : gameMode(gameMode) {
+ // set random pegs for the game
+ this->setPegs();
+ cout << "ANSWER PEGS ARE SET TO: ";
+ for(int peg : this->getAnswer()){
+ this->printPeg(peg);
+ cout << " ";
+ }
+ cout << endl;
+}
+
+/**
+ * Destructor for HitAndBlow class
+ * @param gameMode
+ */
+HitAndBlow::~HitAndBlow() {}
+
+/**
+ * Hit&Blow game home that prompt user to select the options from below:
+ * 1. SINGLE-PLAY MODE
+ * 2. TWO-PLAYER MODE
+ * 3. EXIT GAME
+ */
+void HitAndBlow::gameHome() {
+ cout << "\n********************************************\n";
+ cout << BOLDYELLOW << " HIT & BLOW" << RESETTEXT;
+ cout << "\n********************************************\n";
+ cout << BOLDTEXT << "▼ RULES\n" << RESETTEXT;
+ cout << " Your goal is to guess the correct sequence of 4 colored pegs.\n"
+ " You have 8 attempts to guess. For each attempt, \n"
+ " you will get feedback with the number of HITs and BLOWs\n\n "
+ << BackRED("HIT") << " : right color at right position\n "
+ << BackBLU("BLOW") << " : right color but wrong position\n\n";
+ cout << BOLDTEXT << "▼ SELECT OPTION" << RESETTEXT << endl;
+ cout << " 1. SINGLE-PLAYER MODE\n";
+ cout << " 2. TWO-PLAYER MODE\n";
+ cout << " 3. EXIT GAME\n\n";
+ while(true){
+ cout << FOREGRN << ">>> " << RESETTEXT;
+ string input;
+ getline(cin, input);
+ cout << endl;
+ try{
+ int num = stoi(input);
+ if(num == SINGLEPLAY || num == MULTIPLAY || num == ENDGAME){
+ this->gameMode = num;
+ break;
+ }
+ }catch(exception &e){ }
+ cout<< "INVALID INPUT: ENTER 1 OR 2 OR 3" << endl;
+ }
+}
+
+/**
+ * Ask user to guess the sequence of colored pegs
+ * @return vector number list of user's guess
+ */
+vector HitAndBlow::guess() {
+ // print the attempt no
+ printAttemptNo(getAttempts().size() + 1);
+ cout << "\nENTER 4 NUMBERS SEPARATED BY A SPACE\n";
+ cout << "PEG OPTIONS ▶︎ ";
+ printPegOptions();
+
+ vector attempt;
+ while(true){
+ cout << FOREGRN << "\n>>> " << RESETTEXT;
+ string guess;
+ getline(cin, guess);
+ attempt = processInput(guess, " ");
+
+ if(attempt.size() != 4){
+ cout << "INVALID INPUT\n ";
+ }else{
+ cout << "CONFIRM? (Y/N) ";
+ printPegs(attempt);
+ cout << FOREGRN << ">>> " << RESETTEXT;
+ string res;
+ getline(cin, res);
+ cout << endl;
+ if(res == "y" || res == "Y"){ break; }
+ }
+ }
+ submitGuess(attempt);
+ return attempt;
+}
+
+/**
+ * Print the ending message and ask user to select from continue and end game
+ * @param msgs ending message
+ */
+void HitAndBlow::endGame(vector msgs) {
+ string msg1, msg2;
+ if(msgs.size() == 2){
+ msg1 = msgs[0];
+ msg2 = msgs[1];
+ if(gameMode == MULTIPLAY){
+ msg2 += " & " + players[0];
+ }
+ }
+ cout << endl << endl;
+ cout << "********************************************\n";
+ cout << "********************************************\n\n";
+ cout << FOREYEL << BOLDTEXT << BLINK << " " << msg1 << RESETTEXT << endl << endl;
+ cout << " " << msg2 << endl << endl << endl;
+ cout << " ANSWER IS ";
+ vector ans = this->getAnswer();
+ printPegs(ans);
+ cout << "********************************************\n";
+ cout << "********************************************\n\n";
+ cout << "CONTINUE? (Y/N)" << endl;
+ cout << FOREGRN << ">>> " << RESETTEXT;
+ string res;
+ getline(cin, res);
+ cout << endl;
+ if(res == "y" || res == "Y"){
+ // reset the game
+ resetGame();
+ }else{
+ gameMode = ENDGAME;
+ }
+}
+
+/**
+ * Count the number of hits and blows (Set solved flag to true if hits == 4)
+ * @param pegs number list for pegs
+ * @param hits number of hits
+ * @param blows nmber of blows
+ */
+void HitAndBlow::countHitsBlows(vector pegs, int &hits, int &blows) {
+
+ unordered_set colors;
+
+ for(int i = 0; i < pegs.size(); i ++) {
+ if(this->answer.find(pegs[i]) != this->answer.end()) {
+ if(this->answer[pegs[i]] == i){
+ if(colors.count(pegs[i]) == 1) {
+ hits++;
+ blows--;
+ }
+ else{
+ hits++;
+ }
+ }else{
+ if(colors.count(pegs[i]) == 1) {
+ continue;
+ }
+ else{
+ blows++;
+ }
+ }
+ }
+ colors.insert(pegs[i]);
+ }
+ if(hits==4){
+ setIsSolved(true);
+ }
+
+}
+
+/**
+ * Set the 4 colored pegs in a random order (no duplicate colors)
+ */
+void HitAndBlow::setPegs() {
+ int pos = 0;
+ srand(time(nullptr)); // initialize random seed
+ while(this->answer.size() < 4){
+ int colorNum = 1 + rand() % 6;
+ if(this->answer.find(colorNum) == this->answer.end()){
+ this->answer[colorNum] = pos++;
+ }
+ }
+}
+
+/**
+ * Split a string of integers by space and store them into integer vector.
+ * @param strPegs string to be split into integers
+ * @param delim delimiter
+ * @return integer vector
+ */
+vector HitAndBlow::processInput(string strPegs, string delim) {
+ vector strs = splitStr(strPegs, delim);
+ vector pegs;
+ try{
+ for(auto s : strs){
+ int num = stoi(s);
+ if(num >= 1 && num <=6){
+ pegs.push_back(num);
+ }
+ }
+ }catch (exception &e){}
+ return pegs;
+}
+
+/**
+ * Split a string by specified delimiter
+ * @param str
+ * @param delim
+ * @return a vector of strings
+ */
+vector HitAndBlow::splitStr(string str, string delim){
+ vector list;
+ size_t pos = 0;
+ while((pos = str.find(delim)) != string::npos){
+ list.push_back(str.substr(0, pos));
+ str.erase(0, pos + delim.length());
+ }
+ if(str.length()!=0){
+ list.push_back(str);
+ }
+ return list;
+}
+
+/**
+ * Print the specified peg
+ * @param pegNum number of a peg
+ */
+void HitAndBlow::printPeg(int pegNum) {
+ switch(pegNum){
+ case PEGRED:
+ cout << FORERED "❶" << RESETTEXT;
+ break;
+ case PEGYEL:
+ cout << FOREYEL "❷" << RESETTEXT;
+ break;
+ case PEGCYN:
+ cout << FORECYN "❸" << RESETTEXT;
+ break;
+ case PEGGRN:
+ cout << FOREGRN "❹" << RESETTEXT;
+ break;
+ case PEGMAG:
+ cout << FOREMAG "❺" << RESETTEXT;
+ break;
+ case PEGWHT:
+ cout << FOREWHT "❻️" << RESETTEXT;
+ break;
+ }
+}
+
+/**
+ * Print the specified pegs
+ * @param pegs list of pegs
+ */
+void HitAndBlow::printPegs(vector pegs) {
+ for(auto p : pegs){
+ printPeg(p);
+ cout << " ";
+ }
+ cout << endl;
+}
+
+/**
+ * Print peg options
+ */
+void HitAndBlow::printPegOptions() {
+ for(int i = 1; i <=6 ;i++){
+ cout << i << ":";
+ printPeg(i);
+ cout << " ";
+ }
+ cout << endl;
+}
+
+/**
+ * Print the number of attempts
+ * @param num
+ */
+void HitAndBlow::printAttemptNo(int num) {
+ cout << BACKWHT << BOLDBLACK << " ATTEMPT #" << num << " " << RESETTEXT;
+}
+
+/**
+ * Store the guess into attempts list
+ * @param guess
+ */
+void HitAndBlow::submitGuess(vector guess){
+ attempts.push_back(guess);
+}
+
+/**
+ * Reset the game to get ready for a new game
+ */
+void HitAndBlow::resetGame() {
+ gameMode = HOME;
+ answer.clear();
+ players.clear();
+ attempts.clear();
+ nextPlayer = -1;
+ isSolved = false;
+}
+
+/**
+ * Print the number of hits and blows
+ * @param hits
+ * @param blows
+ */
+void HitAndBlow::printHitsBlows(int hits, int blows){
+ cout << " " << BACKRED << "HIT: " << hits << RESETTEXT << " "
+ << BACKBLU << "BLOW: " << blows << RESETTEXT << endl << endl;;
+}
+
+/**
+ * Getter for answer pegs
+ * @return
+ */
+vector HitAndBlow::getAnswer() {
+ vector ans(4);
+ for(auto it : this->answer){
+ ans[(it).second] = (it).first;
+ }
+ return ans;
+}
+
+/**
+ * Setter for answer pegs
+ * @param answer
+ */
+void HitAndBlow::setAnswer(const vector &answer) {
+ for(int i = 0; i < answer.size(); i++){
+ this->answer[answer[i]] = i;
+ }
+}
+
+/**
+ * Getter for isSolved
+ * @return
+ */
+bool HitAndBlow::getIsSolved() const {
+ return isSolved;
+}
+
+/**
+ * Setter for isSolved
+ * @param isSolved
+ */
+void HitAndBlow::setIsSolved(bool isSolved) {
+ HitAndBlow::isSolved = isSolved;
+}
+
+/**
+ * Getter for gameMode
+ * @return
+ */
+int HitAndBlow::getGameMode() const {
+ return gameMode;
+}
+
+/**
+ * Setter for setMode
+ * @param gameMode
+ */
+void HitAndBlow::setGameMode(int gameMode) {
+ HitAndBlow::gameMode = gameMode;
+}
+
+/**
+ * Getter for attemps
+ * @return
+ */
+const vector< vector > &HitAndBlow::getAttempts() const {
+ return attempts;
+}
+
+/**
+ * Getter for players
+ * @return
+ */
+const unordered_map &HitAndBlow::getPlayers() const {
+ return players;
+}
+
+/**
+ * Getter for nextPlayer (used only in 2-player mode)
+ * @return
+ */
+int HitAndBlow::getNextPlayer() const {
+ return nextPlayer;
+}
+
+/**
+ * Setter for nextPlayer (used only in 2-player mode)
+ * @param nextPlayer
+ */
+void HitAndBlow::setNextPlayer(int nextPlayer) {
+ HitAndBlow::nextPlayer = nextPlayer;
+}
+
+/**
+ * Add a player to the game (used only in 2-player mode)
+ * @param playerNo
+ * @param playerName
+ */
+void HitAndBlow::addPlayer(int playerNo, string playerName) {
+ players.insert(make_pair(playerNo, playerName));
+}
+
+/**
+ * Remove a player from the game (used only in 2-player mode)
+ * @param playerNo
+ */
+void HitAndBlow::removePlayer(int playerNo) {
+ players.erase(playerNo);
+}
+
+/**
+ * Get the player number for another player in the game (used only in 2-player mode)
+ * @param playerNo
+ * @return
+ */
+int HitAndBlow::getAnotherPlayer(int playerNo){
+ int anotherPlayer = -1;
+ for(auto player : players){
+ if(player.first != playerNo){
+ anotherPlayer = player.first;
+ }
+ }
+ return anotherPlayer;
+}
diff --git a/src/utility/HitAndBlow.h b/src/utility/HitAndBlow.h
new file mode 100644
index 0000000..076760f
--- /dev/null
+++ b/src/utility/HitAndBlow.h
@@ -0,0 +1,85 @@
+#ifndef HITANDBLOW_HITANDBLOW_H
+#define HITANDBLOW_HITANDBLOW_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include