// Name: Sandipsinh Rathod // Email: sdr5549@psu.edu // Class: CMPSC 330 // Program Dots and Boxes // Current Date: 11/10/24 7:24 PM // Due Date: 11/10/24 11:59 PM // // Description: This program will take a file as input and // will play the game of dots and boxes. // #include #include #include #include #define pt std::cout << #define pter std::cerr << #define nl "\n" using namespace std; // TODO: spam comments // -------- START PAIR IMPL -------- template struct myPair { T1 first; T2 second; myPair() : first(), second() { } myPair(const T1 &first, const T2 &second) : first(first), second(second) { } }; // -------- END PAIR IMPL -------- // -------- START HELPER FUNCTIONS -------- void swapChar(char &a, char &b) { if (a == b) { return; } a ^= b; b ^= a; a ^= b; } // Recursively reverse a string void reverseString(string &str, int start, int end) { if (start >= end) { return; } swapChar(str[start], str[end]); reverseString(str, start + 1, end - 1); } string toString(int x) { if (!x) return "0"; string result; while (x) { result.push_back(x % 10 + '0'); x /= 10; } reverseString(result, 0, result.size() - 1); return result; } int max(int a, int b) { return a > b ? a : b; } // -------- END HELPER FUNCTIONS -------- // -------- START RAII HELPERS-------- unsigned long allocatedMem = 0; void *alloc(size_t size) { void *ptr = malloc(size); if (!ptr) { pter "Memory allocation failed" << nl; return NULL; } allocatedMem += 1; return ptr; } void dealloc(void *ptr) { if (ptr) free(ptr); allocatedMem -= 1; } bool assert_alloc() { return !allocatedMem; } // -------- END RAII HELPERS-------- // -------- START SIMPLE VEC IMPLEMENTATION -------- template class myVec { T *data; size_t capacity; size_t length; void resize() { if (length == capacity || !length) { capacity = capacity ? capacity << 1 : 1; T *newData = static_cast(alloc(capacity * sizeof(T))); for (size_t i = 0; i < length; i++) { newData[i] = data[i]; } dealloc(data); data = newData; } } public: myVec() { capacity = 0; length = 0; data = NULL; } myVec(size_t size) { capacity = size; length = 0; data = static_cast(malloc(size * sizeof(T))); } void push_back(const T &val) { resize(); data[length++] = val; } void pop_back() { if (length) { length--; } } T &operator[](size_t index) { return data[index]; } int size() { return length; } }; // -------- END SIMPLE VEC IMPLEMENTATION -------- // Struct to store a move struct Move { char player; int moveX; int moveY; }; // Struct to store/serialize the moves struct Moves { int x; int y; myVec moves; }; // Check if the player is valid bool isValidPlayer(char c) { return c >= 'A' && c <= 'Z' && c != 'X'; } // Serialize the input string Moves serialize(char *s) { Moves result; istringstream iss(s); iss >> result.x >> result.y; if (result.x < 2 || result.y < 2) { string err = "Invalid board size: "; err.append(toString(result.x)); err.append("x"); err.append(toString(result.y)); throw invalid_argument(err); } result.x = (result.x << 1) - 1; result.y = (result.y << 1) - 1; char *player = static_cast(alloc(4 * sizeof(char))); int x, y; while (iss >> player >> x >> y) { if (!strcmp(player, "END")) break; // TODO: move this exceptions to runtime checks if (x >= result.x || y >= result.y) { string err = "Invalid move by: "; err.append(player); err.append(" at (x, y): ("); err.append(toString(x)); err.append(", "); err.append(toString(y)); err.append(")"); throw invalid_argument(err); } if (!isValidPlayer(player[0])) { string err = "Invalid player: "; err.push_back(player[0]); throw invalid_argument(err); } Move move; move.player = player[0]; move.moveX = x; move.moveY = y; result.moves.push_back(move); } dealloc(player); return result; } // Allocate the board char **allocBoard(int x, int y) { char **board = static_cast(alloc(x * sizeof(char *))); for (int i = 0; i < x; ++i) { board[i] = static_cast(alloc(y * sizeof(char))); } return board; } // Initialize the board by space or dot void initBoard(char **board, int x, int y) { for (int i = 0; i < x; ++i) { for (int j = 0; j < y; j++) { board[i][j] = (j & 1 || i & 1) ? ' ' : '.'; } } } // Allocate and initialize the board char **allocAndInitBoard(int x, int y) { char **board = allocBoard(x, y); initBoard(board, x, y); return board; } // Deallocate the board void deallocBoard(char **board, int x) { for (int i = 0; i < x; ++i) { dealloc(board[i]); } dealloc(board); } // Print the board void printBoard(char **board, int x, int y) { pt " "; for (int col = 0; col < y; ++col) { if (!(col % 10)) { pt (col / 10); } else { pt " "; } } pt endl; pt " "; for (int col = 0; col < y; ++col) { pt (col) % 10; } pt endl; // Print the board with row numbers for (int row = 0; row < x; ++row) { if (!(row % 10)) { pt row / 10; } else { pt " "; } pt row % 10 << " "; for (int col = 0; col < y; ++col) { pt board[row][col]; } pt endl; } } // Convert the character to lower case char toLowerCase(char c) { return c | 32; } // Normalize the character to [0, 25) int normalize(char c) { return toLowerCase(c) - 'a'; } // Print the scores of the players void printScores(int *points, char blacklist) { myPair > *arr = static_cast > *>(alloc( 26 * sizeof(myPair >))); for (int i = 0; i < 26; i++) { myPair > p; myPair p1; p1.first = points[i]; p1.second = false; p.first = static_cast(i + 'A'); p.second = p1; arr[i] = p; } int maxScore = -1; int maxCount = 0; for (int i = 0; i < 26; i++) { if (arr[i].second.first > maxScore) { maxScore = arr[i].second.first; maxCount = 1; } else if (arr[i].second.first == maxScore) { maxCount++; } } for (int i = 0; i < 26; i++) { if (arr[i].second.first != -1) { pt "Player " << arr[i].first << " has " << arr[i].second.first << (arr[i].second.first < 2 ? " box" : " boxes"); if (arr[i].second.first == maxScore) { if (maxCount > 1) { pt " (tie)"; } else if (arr[i].first != blacklist) { pt " (win)"; } } pt nl; } } dealloc(arr); pt std::endl; } // Check if the move is valid // A move is valid if x%2 != y%2 and the cell is empty bool isValidMove(int x, int y, char **board) { return (x & 1) != (y & 1) && board[x][y] == ' '; } // Add a point to the player and update the board void addPoint( int *points, char **board, int x, int y, char player ) { if (board[x][y] == ' ') { if (points[normalize(player)] == -1) { points[normalize(player)] = 1; } else { points[normalize(player)]++; } board[x][y] = player; } } // check for any box on the top or bottom of the current move void solveVertical( int moveX, int moveY, char **board, int *points, int x, int y, char player ) { // check for any box below current move if (moveX < x - 2 && board[moveX + 2][moveY] != ' ' && board[moveX + 1][moveY - 1] != ' ') { int rightEdge = moveY == y - 1 ? 1 : board[moveX + 1][moveY + 1] != ' '; if (rightEdge) { addPoint(points, board, moveX + 1, moveY, player); } } // check for any box above current move if (moveX > 1 && board[moveX - 2][moveY] != ' ' && board[moveX - 1][moveY - 1] != ' ') { int rightEdge = moveY == y - 1 ? 1 : board[moveX - 1][moveY + 1] != ' '; if (rightEdge) { addPoint(points, board, moveX - 1, moveY, player); } } } // check for any box on the left or right of the current move void solveHorizontal( int moveX, int moveY, char **board, int *points, int y, char player ) { if (moveY) { if (moveY == y - 1) { if (board[moveX][moveY - 2] != ' ' && ( board[moveX + 1][moveY - 1] != ' ' && board[moveX - 1][moveY - 1] != ' ')) { addPoint(points, board, moveX, moveY - 1, player); } } else { if (board[moveX][moveY - 2] != ' ' || board[moveX][moveY + 2] != ' ') { if (board[moveX + 1][moveY - 1] != ' ' && board[moveX - 1][moveY - 1] != ' ') { addPoint(points, board, moveX, moveY - 1, player); } if (board[moveX + 1][moveY + 1] != ' ' && board[moveX - 1][moveY + 1] != ' ') { addPoint(points, board, moveX, moveY + 1, player); } } } } else { if (board[moveX][moveY + 2] != ' ' && (board[moveX + 1][moveY + 1] != ' ' && board[moveX - 1][moveY + 1] != ' ')) { addPoint(points, board, moveX, moveY + 1, player); } } } // Solve the game void solve(Moves moves, char **board) { int *points = static_cast(alloc(26 * (sizeof(int)))); for (int i = 0; i < 26; i++) { points[i] = -1; } for (int i = 0; i < moves.moves.size(); i++) { Move move = moves.moves[i]; int moveX = move.moveX; int moveY = move.moveY; char player = move.player; points[normalize(player)] = max(0, points[normalize(player)]); if (!isValidMove(moveX, moveY, board)) { pt "Invalid or Repeated move by: " << player << " at (x, y): (" << moveX << ", " << moveY << ")" << nl; board[moveX][moveY] = 'X'; printBoard(board, moves.x, moves.y); printScores(points, player); pt "Exiting.." << nl; return; } board[moveX][moveY] = toLowerCase(player); if (!(moveX & 1) && (moveY & 1)) { solveVertical(moveX, moveY, board, points, moves.x, moves.y, player); } else if ((moveX & 1) && !(moveY & 1)) { solveHorizontal(moveX, moveY, board, points, moves.y, player); } else { pt "Invalid move by: " << player << " at (x, y): (" << moveX << ", " << moveY << ")" << nl; board[moveX][moveY] = 'X'; printBoard(board, moves.x, moves.y); printScores(points, player); pt "Exiting.." << nl; return; } } printBoard(board, moves.x, moves.y); printScores(points, '\0'); dealloc(points); } // Run the game int run(char *fileName) { // --- IO START --- FILE *inp = fopen(fileName, "r"); if (!inp) { pter "File not found" << nl; return 1; } fseek(inp, 0L, SEEK_END); const long sz = ftell(inp); fseek(inp, 0L, SEEK_SET); char *fileInpPtr = static_cast(alloc(sz + 1 * sizeof(char))); fileInpPtr[sz] = '\0'; for (int i = 0; i < sz; ++i) { fscanf(inp, "%c", &fileInpPtr[i]); } // --- IO END --- // --- ALGORITHM START --- Moves moves = serialize(fileInpPtr); char **board = allocAndInitBoard(moves.x, moves.y); solve(moves, board); // --- ALGORITHM END --- // --- MEMORY CLEANUP START --- dealloc(fileInpPtr); deallocBoard(board, moves.x); fclose(inp); if (!assert_alloc()) { pter "Memory leak detected" << nl; return 1; } // --- MEMORY CLEANUP END --- return 0; } int main(int argc, char **argv) { if (argc < 2) { pter "No input file provided" << nl; pter "Usage: dotsandboxes /path/to/input.txt" << nl; return 1; } // collect errors at single place try { return run(argv[1]); } catch (const invalid_argument &e) { pter e.what() << nl; return 1; } }