This commit is contained in:
Sandipsinh Rathod 2024-12-06 21:24:11 -05:00
parent 3ecbd34211
commit a6a1fca8cd
No known key found for this signature in database
10 changed files with 75 additions and 396 deletions

61
README.txt Normal file

@ -0,0 +1,61 @@
Team member 1:
Name: Sandipsinh Rathod
Email: sdr5549@psu.edu
Class: CMPSC 330
Team member 2:
Name: Sapan Shah
Email: scs6041@psu.edu
Class: CMPSC 330
Homework 5
Due Date: December 6, 2024
Command used to create object file for strategic player
g++ -O3 -shared -fPIC -ansi -pedantic -std=c++14 board.cxx ss.cxx -o SandipsinhRathodsdr5549_SapanShahscs6041.so
Restructuring HW2 and HW4 in HW5
1. Class Abstraction and Polymorphism
HW2: The game logic was directly implemented without clear abstractions for players, relying on procedural logic.
HW4: Introduced polymorphism via Player, RandomPlayer, and StrategicPlayer classes. Players had distinct behavior, and polymorphism allowed dynamic handling of different player types.
HW5: Built on this by creating the StrategicPlayer1 class derived from IPlayer, implementing a robust and modular strategy for player decisions:
Interface Implementation: StrategicPlayer1 implements IPlayer, ensuring that all player-related behaviors are well-defined and standardized across implementations.
Enhanced Strategies: Added sophisticated methods like FindBoxCompletingMove and ForceOpponentMistake, making the strategy logic adaptable and focused on optimizing game outcomes.
2. Dynamic Memory Management
HW2: Basic memory management using raw pointers with manual deallocation.
HW4: Introduced the RAII (Resource Acquisition Is Initialization) pattern for managing board memory allocation and deallocation efficiently.
HW5: Continued RAII principles with a more structured approach to object lifecycles, ensuring robust cleanup:
Explicit initialization and cleanup methods (Init, Close) for the StrategicPlayer1 class.
Use of dynamic memory in a controlled and predictable way, minimizing risks of memory leaks.
3. Board Representation
HW2: The board was represented as a dynamically allocated 2D array, with logic scattered across functions.
HW4: Encapsulated board management into the Board class, which included methods for placing lines, checking box completion, and printing the board.
HW5: Enhanced the Board class functionality:
Added methods like ListEmptyLines to provide the player class with detailed insights into game state.
Improved the interface between the board and player, enabling complex strategic evaluations such as chain creation and cost evaluation.
4. Player Strategies
HW2: All moves were processed in a straightforward, procedural manner without differentiation between players.
HW4: Differentiated players using classes for random and strategic play. However, the strategic player logic was limited to basic box completion.
HW5: Refined the strategic player logic significantly:
Multi-step Decision Making: Implemented a three-step approach for move selection: completing boxes, avoiding chains, and fallback to optimal moves.
Move Evaluation: Added sophisticated cost evaluation (EvaluateMoveCost) and chain detection (DoesMoveCreateChain).
5. Event-Driven Design
HW4: The player and board interactions were tightly coupled.
HW5: Introduced event-driven methods (EventAddLine, EventAddBox) to separate game state updates from decision-making logic. This enhances modularity and makes the code easier to extend and debug.
Learnings
Modularity and Reusability: Encapsulation of functionality (e.g., board management, player behaviors) allows for better reuse and testing. The separation of concerns between classes improved maintainability.
Polymorphism and Interfaces: Leveraging polymorphism (IPlayer) provided a flexible framework to implement diverse player strategies while ensuring consistency.
Strategic Thinking: Developing a multi-step strategy for players helped refine algorithmic thinking and fostered a deeper understanding of game theory.
RAII and Memory Management: The shift to controlled memory allocation and deallocation in RAII patterns reduced errors and improved reliability.
Event-Driven Programming: Designing the game logic around events encouraged a cleaner architecture and reduced tight coupling.
Iterative Refinement: By analyzing and building upon previous homework, we demonstrated the importance of iterative development and incremental improvement in programming.

BIN
bar.so

Binary file not shown.

BIN
foo.so

Binary file not shown.

@ -1,122 +0,0 @@
#include "custom_player.h"
#include <iostream>
#include <cstdlib> // for rand
#include <ctime> // for time
using namespace std;
// Factory function for dynamic library loading
extern "C" IPlayer* PlayerFactory() {
return new CustomPlayer();
}
// Constructor
CustomPlayer::CustomPlayer() {
srand(time(0));
}
// Destructor
CustomPlayer::~CustomPlayer() {}
// Initializes the player with the board size and player symbols
void CustomPlayer::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;
empty_lines = new Loc[board.GetRows() * board.GetCols()];
}
// Cleans up dynamically allocated memory
void CustomPlayer::Close() {
board.FreeBoard();
delete[] empty_lines;
}
// Returns the player info
std::string CustomPlayer::PlayerInfo() {
return "Custom Strategic Player";
}
// Updates the board when a line is added
void CustomPlayer::EventAddLine(char bar, const Loc& loc) {
board(loc) = bar;
}
// Updates the board when a box is completed
void CustomPlayer::EventAddBox(char box, const Loc& loc) {
board(loc) = box;
}
// Updates the list of available line locations
void CustomPlayer::ListEmptyLines() {
empty_lines_count = 0;
for (int r = 0; r < board.GetRows(); r++) {
for (int c = 0; c < board.GetCols(); c++) {
if (board(r, c) == ' ' && Loc(r, c).IsLineLocation()) {
empty_lines[empty_lines_count++] = Loc(r, c);
}
}
}
}
// Helper function to check if a line completes a box
bool CustomPlayer::DoesLineCompleteBox(const Loc& loc) {
if (loc.IsLineHorizontalLocation()) {
if (loc.row > 0 && board(loc.row - 1, loc.col) != ' ' &&
board(loc.row - 1, loc.col - 1) != ' ' && board(loc.row - 1, loc.col + 1) != ' ')
return true;
if (loc.row < board.GetRows() - 1 && board(loc.row + 1, loc.col) != ' ' &&
board(loc.row + 1, loc.col - 1) != ' ' && board(loc.row + 1, loc.col + 1) != ' ')
return true;
} else if (loc.IsLineVerticalLocation()) {
if (loc.col > 0 && board(loc.row, loc.col - 1) != ' ' &&
board(loc.row - 1, loc.col - 1) != ' ' && board(loc.row + 1, loc.col - 1) != ' ')
return true;
if (loc.col < board.GetCols() - 1 && board(loc.row, loc.col + 1) != ' ' &&
board(loc.row - 1, loc.col + 1) != ' ' && board(loc.row + 1, loc.col + 1) != ' ')
return true;
}
return false;
}
// Helper function to avoid giving the opponent a chance to complete a box
bool CustomPlayer::WouldGiveOpponentBox(const Loc& loc) {
// Temporarily add the line to the board
board(loc) = player_line;
// Check if the opponent can complete a box in the next move
for (int r = 0; r < board.GetRows(); r++) {
for (int c = 0; c < board.GetCols(); c++) {
if (board(r, c) == ' ' && Loc(r, c).IsLineLocation() &&
DoesLineCompleteBox(Loc(r, c))) {
board(loc) = ' '; // Revert the move
return true;
}
}
}
// Revert the move
board(loc) = ' ';
return false;
}
// Determines the next move
Loc CustomPlayer::SelectLineLocation() {
ListEmptyLines();
// Strategy: Prioritize moves that complete a box
for (int i = 0; i < empty_lines_count; i++) {
if (DoesLineCompleteBox(empty_lines[i])) {
return empty_lines[i];
}
}
// Avoid moves that give the opponent a chance to complete a box
for (int i = 0; i < empty_lines_count; i++) {
if (!WouldGiveOpponentBox(empty_lines[i])) {
return empty_lines[i];
}
}
// Otherwise, pick a random move
return empty_lines[rand() % empty_lines_count];
}

@ -1,54 +0,0 @@
#ifndef __CUSTOM_PLAYER__
#define __CUSTOM_PLAYER__
#include "player.h"
#include "common.h"
#include "board.h"
#include <string>
#include <vector>
class CustomPlayer : public IPlayer {
private:
Board board; // Game board to track state
char player_box; // Character representing the player's boxes
char player_line; // Character representing the player's lines
Loc* empty_lines; // Array to store available line locations
int empty_lines_count; // Number of available line locations
public:
CustomPlayer();
~CustomPlayer();
// Initializes the player with board details and player symbols
void Init(int dots_in_rows, int dots_in_cols, char player_box, char player_line) override;
// Cleans up dynamically allocated memory
void Close() override;
// Returns the player information
std::string PlayerInfo() override;
// Called when a line is added to the board
void EventAddLine(char bar, const Loc& loc) override;
// Called when a box is completed on the board
void EventAddBox(char box, const Loc& loc) override;
// Determines the next move based on the current state of the board
Loc SelectLineLocation() override;
private:
// Updates the list of available line locations
void ListEmptyLines();
// Helper function to check if a line completes a box
bool DoesLineCompleteBox(const Loc& loc);
// Helper function to avoid giving the opponent a chance to complete a box
bool WouldGiveOpponentBox(const Loc& loc);
// Evaluate the best line location strategically
Loc GetBestStrategicMove();
};
#endif

@ -1,10 +0,0 @@
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

@ -1,4 +1,17 @@
#include "strategic_player1.h"
// Team member 1
// Name: Sandipsinh Rathod
// Email: sdr5549@psu.edu
// Team member 2
// Name: Sapan Shah
// Email: scs6041@psu.edu
//
// Program homework 5
// Class: CMPSC 330
// Current Date: 6/12/24 9:15 PM
// Due Date: 6/12/24 11:59 PM
#include "ss.h"
extern "C" IPlayer *PlayerFactory() {
return new StrategicPlayer1();

@ -1,171 +0,0 @@
#include "strategic_player.h"
#include "common.h"
extern "C" IPlayer* PlayerFactory()
{
return new StrategicPlayer();
}
// Helper functions
inline int normalize(int x) {
return (x + 1) >> 1;
}
inline char getPoint(Board &board, const int row, const int col) {
return board(row, col);
}
inline 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) == ' ');
}
inline void set(Board &board, int r, int c, char ch) {
board(r, c) = ch;
}
//
string StrategicPlayer::PlayerInfo() {
return "Sandipsinh Rathod (sdr5549@psu.edu), Sapan Shah (scs6041@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();
}
/// 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);
}
inline void selectLine(Board &board, int &row, int &col, int rows, int cols, char name) {
int max_row = 2 * rows - 2;
int max_col = 2 * cols - 2;
// Step 1: Try to complete a box
#pragma omp parallel for collapse(2)
for (int r = 1; r < max_row; r += 2) {
// Iterate over box centers (odd rows)
for (int c = 1; c < max_col; 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
#pragma omp parallel for collapse(2)
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 < max_row; nr += 2) {
// Iterate over box centers
for (int nc = 1; nc < max_col; nc += 2) {
if (getPoint(board, nr, nc) == ' ') {
int adjacentLines = 0;
if (nr > 0 && getPoint(board, nr - 1, nc) != ' ') adjacentLines++; // Top line
if (nr < max_row && getPoint(board, nr + 1, nc) != ' ') adjacentLines++; // Bottom line
if (nc > 0 && getPoint(board, nr, nc - 1) != ' ') adjacentLines++; // Left line
if (nc < max_col && 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
#pragma omp parallel for collapse(2)
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);
}

@ -1,38 +0,0 @@
#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