commit c1c4586abd66c9b1aa2c259d512ccf2ad0c87934 Author: Sandipsinh Rathod Date: Fri Dec 6 17:06:18 2024 -0500 init diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4b4f67c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.29) +project(cmpsc330hw5) + +set(CMAKE_CXX_STANDARD 20) + +add_executable(cmpsc330hw5 + main.cxx + board.cxx + dotsboxesgm.cxx + strategic_player.h + strategic_player.cxx +) diff --git a/board.cxx b/board.cxx new file mode 100644 index 0000000..31c4d81 --- /dev/null +++ b/board.cxx @@ -0,0 +1,74 @@ +#include +#include +#include "common.h" +#include "board.h" + +using namespace std; + +void Board::AllocateBoard(int dots_in_rows, int dots_in_cols, int& blanklinecount) +{ + assert(board == nullptr); + rows = dots_in_rows * 2 - 1; + cols = dots_in_cols * 2 - 1; + + board = new char* [rows]; + for(int r = 0; r < rows; r++) + board[r] = new char[cols]; + + blanklinecount = 0; + for(int r = 0; r < rows; r ++) + for(int c = 0; c < cols; c ++) + { + board[r][c] = ' '; + if(Loc(r, c).IsLineLocation()) + blanklinecount++; + } + + for(int r = 0; r < rows; r += 2) + for(int c = 0; c < cols; c += 2) + board[r][c] = '.'; +} + +void Board::FreeBoard() +{ + if(board != nullptr) + { + for(int r = 0; r < rows; r++) + delete[] board[r]; + delete[] board; + board = nullptr; + } +} + +ostream& operator << (ostream& os, const Board& board) +{ + cout << " "; + for(int i=0; i +#include +using namespace std; + +class Loc +{ +public: + int row, col; + Loc() { row = -1; col = -1; } + Loc(const Loc& loc) { row = loc.row; col = loc.col; } + Loc(int nr, int nc) { row = nr; col = nc; } + bool IsDotLocation() const { return ((row % 2 == 0) && (col % 2 == 0)); } + bool IsBoxLocation() const { return ((row % 2 == 1) && (col % 2 == 1)); } + bool IsLineHorizontalLocation() const { return ((row % 2 == 0) && (col % 2 == 1)); } + bool IsLineVerticalLocation() const { return ((row % 2 == 1) && (col % 2 == 0)); } + bool IsLineLocation() const { return IsLineHorizontalLocation() || IsLineVerticalLocation(); } +}; + +#endif diff --git a/dotsboxesgm.cxx b/dotsboxesgm.cxx new file mode 100644 index 0000000..70c20eb --- /dev/null +++ b/dotsboxesgm.cxx @@ -0,0 +1,306 @@ +#include +#include +#include +#include +#include +#include "common.h" +#include "board.h" +#include "player.h" + + +void ClearConsole(); +void SleepInSec(double sleep_sec); +extern void* a_player_libhandle; +extern void* b_player_libhandle; +string LoadPlayer(IPlayer*& ab_player, string libpath, int board_rows, int board_cols, char box_type, char line_type); +void CloseLibs(); + +struct PlayerInfo +{ +public: + IPlayer* iplayer; + int score; + char box; + char line; + double timespent; + PlayerInfo* next; // play-list (linked list) + PlayerInfo* prev; // play-list (linked list) + PlayerInfo(IPlayer* p, char b, char l) + { + iplayer = p; + score = 0; + box = b; + line = l; + timespent = 0; + } +}; + +void PrintBoard(int iter, const Board& board, const vector& players) +{ + // print board + cout << board; + cout << endl; + + // print iteration number + cout << "iter : " << iter << endl; + + // find winning scores and winner numbers + int maxscore = 0; + int maxscore_cnt = 0; + for(int i=0; iPlayerInfo() << ") has "; + if(players[i].score >= 2) cout << players[i].score << " boxes"; + else cout << players[i].score << " box"; + cout << " and spent " << players[i].timespent*100 << " msec"; + if(maxscore == players[i].score) + { + if(maxscore_cnt == 1) cout << " (win)." << endl; + else cout << " (tie)." << endl; + } + else + { + cout << "." << endl; + } + } + cout << endl; +} + +// send add-line event to players +void EventAddLine(const vector& players, char line, const Loc& loc) +{ + for(int j = 0; j < players.size(); j++) + { + if(players[j].next == nullptr) + continue; + players[j].iplayer->EventAddLine(line, loc); + } +} + +// send add-box event to players +void EventAddBox(const vector& players, char box, const Loc& loc) +{ + for(int j = 0; j < players.size(); j++) + { + if(players[j].next == nullptr) + continue; + players[j].iplayer->EventAddBox(box, loc); + } +} + +// update box with player box info, and return true (for gaining box) or false (for not gaining box) +bool AddBox(Board& board, int row, int col, char box, const vector& players) +{ + assert(Loc(row,col).IsBoxLocation()); + if(board(row,col) == ' ' + && board(row+1,col) != ' ' + && board(row-1,col) != ' ' + && board(row,col+1) != ' ' + && board(row,col-1) != ' ') + { + board(row,col) = box; + EventAddBox(players, box, Loc(row,col)); + return true; + } + return false; +} + +string GameMaster(const vector& libpaths, vector& scores, vector& timespents, double sleep_sec) +{ + /////////////////////////////////////////////////// + // create board + int board_rows, board_cols, board_blanklinecount; + cin >> board_rows >> board_cols; + cout << endl; + Board board; + board.AllocateBoard(board_rows, board_cols, board_blanklinecount); + + /////////////////////////////////////////////////// + // load players + vector players; + int ibox = 0; + for(int i=0; iClose(); + continue; + } + players.push_back(PlayerInfo(player, box_type, line_type)); + } + catch(...) + { + cout << "Exception while loading a player (" << libpath << ")." << endl; + if(player != nullptr) + player->Close(); + continue; + } + } + + /////////////////////////////////////////////////// + // set player sequence and first player + int iter = 0; + for(int i=0; ibox; + + //////////////////////////////////////////////////////////// + // determine player's line location + // if the player put a line into a invalid location, then makr the player lose + Loc loc; + double timespent = 0; + try + { + auto begin = chrono::high_resolution_clock::now(); + loc = player->iplayer->SelectLineLocation(); + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(end - begin); + player->timespent += (elapsed.count() * 1e-9); + if(loc.IsBoxLocation()) throw "lose"; + if(loc.IsDotLocation()) throw "lose"; + if(board(loc) != ' ' ) throw "lose"; + } + catch(...) + { + // if the player put line into a wrong location or throw exception, + // make the player have -1 score (invalid move) and + // remove the player from play-list (linked-list) + player->score = -1; + if(player->next == player) + { + // if player is the last player, make the linked-list empty + player = nullptr; + } + else + { + // if the player is not the last player, remove the player in the linked-list, and select the next player + PlayerInfo* next_player = player->next; + player->next->prev = player->prev; + player->prev->next = player->next; + player->next = nullptr; + player->prev = nullptr; + player = next_player; + } + // print play-log + if(sleep_sec == 0) + cout << " made an invalid move." << endl; + continue; + } + + //////////////////////////////////////////////////////////// + // add the line into the board + assert(loc.IsLineLocation()); + assert(board(loc) == ' ' ); + board(loc) = player->line; + board_blanklinecount --; + EventAddLine(players, player->line, loc); + + //////////////////////////////////////////////////////////// + // if the player gain a box, then + // mark the box owner and + // make gainbox true + int gainbox = 0; + if(loc.IsLineHorizontalLocation()) + { + if(loc.row-1 >= 0 ) if(AddBox(board, loc.row-1, loc.col, player->box, players)) { player->score++; gainbox++; } + if(loc.row+1 < board.GetRows()) if(AddBox(board, loc.row+1, loc.col, player->box, players)) { player->score++; gainbox++; } + } + if(loc.IsLineVerticalLocation()) + { + if(loc.col-1 >= 0 ) if(AddBox(board, loc.row, loc.col-1, player->box, players)) { player->score++; gainbox++; } + if(loc.col+1 < board.GetCols()) if(AddBox(board, loc.row, loc.col+1, player->box, players)) { player->score++; gainbox++; } + } + + //////////////////////////////////////////////////////////// + // print board or play-log + if(sleep_sec == 0) + { + cout << " add a line at (" << loc.row << "," << loc.col << ")"; + if(gainbox == 1) + cout << " and gain " << gainbox << " box." << endl; + else if(gainbox >= 2) + cout << " and gain " << gainbox << " boxes." << endl; + else + cout << "." << endl; + } + else + { + ClearConsole(); + PrintBoard(iter, board, players); + SleepInSec(sleep_sec); + } + + //////////////////////////////////////////////////////////// + // if the player does not gain a box + // move to the next player + if(gainbox == 0) + player = player->next; + } + + //////////////////////////////////////////////////////////// + // print board + if(sleep_sec != 0) ClearConsole(); + else cout << endl; + PrintBoard(iter, board, players); + + for(int i = 0; i < players.size(); i++) + { + scores [i] = players[i].score; + timespents[i] = players[i].timespent; + } + return ""; +} diff --git a/input1.txt b/input1.txt new file mode 100644 index 0000000..a139c1f --- /dev/null +++ b/input1.txt @@ -0,0 +1,3 @@ +3 8 +A Strategic +B Random diff --git a/main.cxx b/main.cxx new file mode 100644 index 0000000..f4dfa1a --- /dev/null +++ b/main.cxx @@ -0,0 +1,78 @@ +#include +#include +#include "player.h" + +using namespace std; + +#include // usleep() +#include + +string GameMaster(const vector &libpaths, vector &scores, vector ×pents, double sleep_sec); + +void CloseLibs(); + +int main(int argc, char *argv[]) { + // argv[0] : program name + // argv[1] : 1st player library path + // argv[2] : 2nd player library path + // argv[3] : 3rd player library path + if (argc <= 2) + return 0; + + vector libpaths; + vector scores; + vector timespents; + for (int i = 1; i < argc; i++) { + libpaths.push_back(argv[i]); + scores.push_back(0); + timespents.push_back(0); + } + + double sleep_sec = 0.00; + string result = GameMaster(libpaths, scores, timespents, sleep_sec); + CloseLibs(); +} + +void ClearConsole() { + system("clear"); +} + +void SleepInSec(double sleep_sec) { + int sleep_microsecond = sleep_sec * 1000000; + usleep(sleep_microsecond); +} + +vector lst_player_libhandle; + +typedef IPlayer *(*CreatePlayerType)(); + +string LoadPlayer + (IPlayer *&iplayer, string libpath, int board_rows // the size of board (including dots, lines, and boxes) + , int board_cols // the size of board (including dots, lines, and boxes) + , char box_type // the character for the player's boxes + , char line_type // the character for the player's lines + ) { + void *player_libhandle = dlopen(libpath.c_str(), RTLD_LAZY); + if (player_libhandle == nullptr) { + return "cannot load " + libpath; + } + lst_player_libhandle.push_back(player_libhandle); + void *pfunc = dlsym(player_libhandle, "PlayerFactory"); + if (pfunc == nullptr) { + return "cannot find PlayerFactory() function defined as extern C"; + } + CreatePlayerType CreatePlayer = (CreatePlayerType) pfunc; + iplayer = CreatePlayer(); + iplayer->Init(board_rows, board_cols, box_type, line_type); + if (iplayer == nullptr) { + return "cannot create player using PlayerFactory() function"; + } + return ""; +} + +void CloseLibs() { + for (void *player_libhandle: lst_player_libhandle) + dlclose(player_libhandle); + lst_player_libhandle.clear(); +} + diff --git a/player.h b/player.h new file mode 100644 index 0000000..c3987b3 --- /dev/null +++ b/player.h @@ -0,0 +1,35 @@ +#ifndef __PLAYER__ +#define __PLAYER__ + +#include +#include "common.h" +#include "board.h" +using namespace std; + +class IPlayer +{ +public: + // return the player information that includes full name and email address, + // such that "Micky Mouse (mm123@psu.edu)" + virtual string PlayerInfo() = 0; + // Init(const int,const int) will be called before playing the game + // You can create your own data-structure + virtual void Init + ( int board_rows // the size of board (including dots, lines, and boxes) + , int board_cols // the size of board (including dots, lines, and boxes) + , char box_type // the character for the player's boxes + , char line_type // the character for the player's lines + ) = 0; + // Close() will be called after finishing playing the game + // You can remove all dynamically allocated memories + virtual void Close() = 0; + // EventAddLine() and EventAddBox() will be called + // when a player adds a line or when a system assign a box's owner + virtual void EventAddLine(char bar, const Loc& loc) = 0; + virtual void EventAddBox (char box, const Loc& loc) = 0; + // Loc SelectLineLocation() will be called + // when the game system ask where your player want to add a line + virtual Loc SelectLineLocation() = 0; +}; + +#endif diff --git a/random_player.cxx b/random_player.cxx new file mode 100644 index 0000000..26b25bd --- /dev/null +++ b/random_player.cxx @@ -0,0 +1,79 @@ +#include +#include +#include // for srand and rand +#include // for time + +#include "common.h" +#include "player.h" +#include "random_player.h" + +using namespace std; + +extern "C" IPlayer* PlayerFactory() +{ + return new RandomPlayer(); +} + +RandomPlayer::RandomPlayer() +{ + srand(time(0)); +} + +RandomPlayer::~RandomPlayer() +{ +} + +void RandomPlayer::Init(int _dots_in_rows, int _dots_in_cols, char _player_box, char _player_line) +{ + board.AllocateBoard(_dots_in_rows, _dots_in_cols); + player_box = _player_box ; + player_line = _player_line; + emptylines = new Loc[board.GetRows() * board.GetCols()]; +} + +void RandomPlayer::Close() +{ + board.FreeBoard(); + int emptylines_cnt = board.GetRows() * board.GetCols(); + delete[] emptylines; +} + +void RandomPlayer::EventAddLine(const char bar, const Loc& loc) +{ + assert(loc.IsLineLocation()); + assert(board(loc) == ' '); + board(loc) = bar; +} + +void RandomPlayer::EventAddBox(const char box, const Loc& loc) +{ + assert(loc.IsBoxLocation()); + assert(board(loc) == ' '); + board(loc) = box; +} + +Loc RandomPlayer::SelectLineLocation() +{ + ListEmptyLines(); + int randloc = rand() % emptylines_cnt; + return emptylines[randloc]; +} + +void RandomPlayer::ListEmptyLines() +{ + emptylines_cnt = 0; + for(int row=0; row +#include "common.h" +#include "player.h" +#include "board.h" +using namespace std; + +class RandomPlayer : public IPlayer +{ +private: + Board board; + char player_box; + char player_line; + Loc* emptylines; + int emptylines_cnt; +public: + RandomPlayer(); + ~RandomPlayer(); + string PlayerInfo() { return "random player"; } + void Init(int dots_in_rows, int dots_in_cols, char player_box, char player_line); + // Close() will be called after finishing playing the game + // You can remove all dynamically allocated memories + void Close(); + void EventAddLine(char bar, const Loc& loc); + void EventAddBox(char box, const Loc& loc); + Loc SelectLineLocation(); + void ListEmptyLines(); +}; + +#endif diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..215f01e --- /dev/null +++ b/readme.txt @@ -0,0 +1,10 @@ +Build mazerunner program and player_randommove.so library + +g++ -ansi -pedantic -std=c++14 board.cxx dotsboxesgm.cxx main.cxx -o dotsboxes +g++ -shared -fPIC -ansi -pedantic -std=c++14 random_player.cxx board.cxx -o random_player.so + + +playing the dots and boxes games with multiple players + +./dotsboxes ./strategic_player.so ./random_player.so ./strategic_player.so ./random_player.so + diff --git a/strategic_player.cxx b/strategic_player.cxx new file mode 100644 index 0000000..00185cb --- /dev/null +++ b/strategic_player.cxx @@ -0,0 +1,164 @@ +#include "strategic_player.h" +#include "common.h" + +extern "C" IPlayer* PlayerFactory() +{ + return new StrategicPlayer(); +} + + +string StrategicPlayer::PlayerInfo() { + return "Sapan Shah (scs6041@psu.edu), Sandipsinh Rathod (sdr5549@psu.edu)"; +} + +void StrategicPlayer::Init(int board_rows, int board_cols, char box_type, char line_type) { + this->name = box_type; + this->box_name = line_type; + this->board.AllocateBoard(board_rows, board_cols); +} + + +StrategicPlayer::~StrategicPlayer() { + board.FreeBoard(); +} + +void StrategicPlayer::Close() { + board.FreeBoard(); +} + +int normalize(int x) { + return (x + 1) >> 1; +} + +char getPoint(Board &board, const int row, const int col) { + return board(row, col); +} + +bool isLineValid(Board &board, const int row, const int col) { + return (row > -1 && row < board.GetRows() && col > -1 && col < board.GetCols()) && + ((row & 1) != (col & 1)) && (getPoint(board, row, col) == ' '); +} + +void set(Board &board, int r, int c, char ch) { + board(r, c) = ch; +} + + +/// TODO: check if we needs checks :) +void StrategicPlayer::EventAddLine(char bar, const Loc &loc) { + set(board, loc.row, loc.col, bar); +} + +void StrategicPlayer::EventAddBox(char box, const Loc &loc) { + set(board, loc.row, loc.col, box); +} + +void selectLine(Board &board, int &row, int &col, int rows, int cols, char name) { + // Step 1: Try to complete a box + for (int r = 1; r < 2 * rows - 2; r += 2) { + // Iterate over box centers (odd rows) + for (int c = 1; c < 2 * cols - 2; c += 2) { + // Iterate over box centers (odd cols) + // Check adjacent lines for an opportunity to complete a box + if (isLineValid(board, r - 1, c) && // Top line + getPoint(board, r + 1, c) != ' ' && // Bottom line + getPoint(board, r, c - 1) != ' ' && // Left line + getPoint(board, r, c + 1) != ' ') { + // Right line + row = r - 1; + col = c; + return; + } + if (isLineValid(board, r + 1, c) && // Bottom line + getPoint(board, r - 1, c) != ' ' && // Top line + getPoint(board, r, c - 1) != ' ' && // Left line + getPoint(board, r, c + 1) != ' ') { + // Right line + row = r + 1; + col = c; + return; + } + if (isLineValid(board, r, c - 1) && // Left line + getPoint(board, r - 1, c) != ' ' && // Top line + getPoint(board, r + 1, c) != ' ' && // Bottom line + getPoint(board, r, c + 1) != ' ') { + // Right line + row = r; + col = c - 1; + return; + } + if (isLineValid(board, r, c + 1) && // Right line + getPoint(board, r - 1, c) != ' ' && // Top line + getPoint(board, r + 1, c) != ' ' && // Bottom line + getPoint(board, r, c - 1) != ' ') { + // Left line + row = r; + col = c + 1; + return; + } + } + } + + // Step 2: Avoid moves that leave a box with one line remaining + for (int r = 0; r < 2 * rows - 1; ++r) { + // Iterate over all valid rows + for (int c = 0; c < 2 * cols - 1; ++c) { + // Iterate over all valid cols + if (isLineValid(board, r, c)) { + // Simulate placing the line + set(board, r, c, name); + + // Check if this move leaves a box with only one line remaining + bool createsOpportunity = false; + for (int nr = 1; nr < 2 * rows - 2; nr += 2) { + // Iterate over box centers + for (int nc = 1; nc < 2 * cols - 2; nc += 2) { + if (getPoint(board, nr, nc) == ' ') { + int adjacentLines = 0; + if (nr > 0 && getPoint(board, nr - 1, nc) != ' ') adjacentLines++; // Top line + if (nr < 2 * rows - 2 && getPoint(board, nr + 1, nc) != ' ') adjacentLines++; // Bottom line + if (nc > 0 && getPoint(board, nr, nc - 1) != ' ') adjacentLines++; // Left line + if (nc < 2 * cols - 2 && getPoint(board, nr, nc + 1) != ' ') adjacentLines++; // Right line + + if (adjacentLines == 3) { + // Opponent can complete this box + createsOpportunity = true; + break; + } + } + } + if (createsOpportunity) break; + } + + set(board, r, c, ' '); // Undo the simulated move + + if (!createsOpportunity) { + row = r; + col = c; + return; + } + } + } + } + + // Step 3: Fallback to the first valid move + for (int r = 0; r < 2 * rows - 1; ++r) { + for (int c = 0; c < 2 * cols - 1; ++c) { + if (isLineValid(board, r, c)) { + row = r; + col = c; + return; + } + } + } +} + +Loc StrategicPlayer::SelectLineLocation() { + int rows = normalize(board.GetRows()); + int cols = normalize(board.GetCols()); + + int row, col; + selectLine(board, row, col, rows, cols, name); + + return Loc(row, col); +} diff --git a/strategic_player.h b/strategic_player.h new file mode 100644 index 0000000..5ece82e --- /dev/null +++ b/strategic_player.h @@ -0,0 +1,38 @@ + +#ifndef HW4_STRATEGIC_PLAYER_H +#define HW4_STRATEGIC_PLAYER_H + +#include "common.h" +#include "board.h" +#include "player.h" + +class StrategicPlayer: public IPlayer { + char name; + char box_name; + + Board board; +public: + string PlayerInfo(); + // Init(const int,const int) will be called before playing the game + // You can create your own data-structure + void Init + ( int board_rows // the size of board (including dots, lines, and boxes) + , int board_cols // the size of board (including dots, lines, and boxes) + , char box_type // the character for the player's boxes + , char line_type // the character for the player's lines + ); + // Close() will be called after finishing playing the game + // You can remove all dynamically allocated memories + void Close(); + // EventAddLine() and EventAddBox() will be called + // when a player adds a line or when a system assign a box's owner + void EventAddLine(char bar, const Loc& loc); + void EventAddBox (char box, const Loc& loc); + // Loc SelectLineLocation() will be called + // when the game system ask where your player want to add a line + Loc SelectLineLocation(); + + ~StrategicPlayer(); +}; + +#endif //HW4_STRATEGIC_PLAYER_H