From c1c4586abd66c9b1aa2c259d512ccf2ad0c87934 Mon Sep 17 00:00:00 2001 From: Sandipsinh Rathod Date: Fri, 6 Dec 2024 17:06:18 -0500 Subject: [PATCH] init --- CMakeLists.txt | 12 ++ board.cxx | 74 +++++++++++ board.h | 48 +++++++ common.h | 22 ++++ dotsboxesgm.cxx | 306 +++++++++++++++++++++++++++++++++++++++++++ input1.txt | 3 + main.cxx | 78 +++++++++++ player.h | 35 +++++ random_player.cxx | 79 +++++++++++ random_player.h | 32 +++++ readme.txt | 10 ++ strategic_player.cxx | 164 +++++++++++++++++++++++ strategic_player.h | 38 ++++++ 13 files changed, 901 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 board.cxx create mode 100644 board.h create mode 100644 common.h create mode 100644 dotsboxesgm.cxx create mode 100644 input1.txt create mode 100644 main.cxx create mode 100644 player.h create mode 100644 random_player.cxx create mode 100644 random_player.h create mode 100644 readme.txt create mode 100644 strategic_player.cxx create mode 100644 strategic_player.h 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