This commit is contained in:
sapan 2024-11-19 18:54:26 -05:00
commit f99efc4208
11 changed files with 546 additions and 0 deletions

16
CMakeLists.txt Normal file

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.27)
project(hw4)
set(CMAKE_CXX_STANDARD 17)
add_executable(hw4 main.cpp
board.h
random_player.h
strategic_player.h
gameplay.h
board.cpp
random_player.cpp
strategic_player.cpp
gameplay.cpp
gameplay.cpp
player.h)

163
board.cpp Normal file

@ -0,0 +1,163 @@
#include "board.h"
#include <iostream>
#include <cctype>
using namespace std;
Board::Board(int rows, int cols) : rows(rows), cols(cols) {
// Allocate memory for the 2D board
board = new char*[2 * rows - 1];
for (int i = 0; i < 2 * rows - 1; i++) {
board[i] = new char[2 * cols - 1];
for (int j = 0; j < 2 * cols - 1; j++) {
// Initialize with dots at intersections and spaces elsewhere
board[i][j] = (i % 2 == 0 && j % 2 == 0) ? '.' : ' ';
}
}
}
Board::~Board() {
// TODO: use free
// Free dynamically allocated memory
for (int i = 0; i < 2 * rows - 1; i++) {
delete[] board[i];
}
delete[] board;
}
char Board::get(int row, int col) const {
return board[row][col];
}
void Board::set(int row, int col, char value) {
board[row][col] = value;
}
bool Board::isLineValid(int row, int col) const {
// A line is valid if it's in a valid position and not already occupied
return (row > - 1 && row < 2 * rows - 1 && col > -1 && col < 2 * cols - 1) &&
((row&1) != (col&1)) && (board[row][col] == ' ');
}
void Board::placeLine(int row, int col, char player) {
if (isLineValid(row, col)) {
board[row][col] = tolower(player); // Place the player's initial in lowercase
}
}
int Board::checkAndMarkBox(int row, int col, char player) {
int completedBoxes = 0;
// Check if the move is horizontal or vertical
bool isHorizontal = (row % 2 == 0);
if (isHorizontal) {
// Check box above the line (if row > 0)
if (row > 0 &&
board[row - 2][col] != ' ' && // Top line
board[row][col] != ' ' && // Bottom line
board[row - 1][col - 1] != ' ' && // Left line
board[row - 1][col + 1] != ' ') { // Right line
board[row - 1][col] = player; // Mark box with player's initial
completedBoxes++;
}
// Check box below the line (if row < 2 * rows - 2)
if (row < 2 * rows - 2 &&
board[row + 2][col] != ' ' && // Bottom line
board[row][col] != ' ' && // Top line
board[row + 1][col - 1] != ' ' && // Left line
board[row + 1][col + 1] != ' ') { // Right line
board[row + 1][col] = player; // Mark box with player's initial
completedBoxes++;
}
} else {
// Check box to the left (if col > 0)
if (col > 0 &&
board[row - 1][col - 1] != ' ' && // Top line
board[row + 1][col - 1] != ' ' && // Bottom line
board[row][col - 2] != ' ' && // Left line
board[row][col] != ' ') { // Right line
board[row][col - 1] = player; // Mark box with player's initial
completedBoxes++;
}
// Check box to the right (if col < 2 * cols - 2)
if (col < 2 * cols - 2 &&
board[row - 1][col + 1] != ' ' && // Top line
board[row + 1][col + 1] != ' ' && // Bottom line
board[row][col + 2] != ' ' && // Right line
board[row][col] != ' ') { // Left line
board[row][col + 1] = player; // Mark box with player's initial
completedBoxes++;
}
}
return completedBoxes;
}
bool Board::isFull() const {
for (int i = 0; i < 2 * rows - 1; i++) {
for (int j = 0; j < 2 * cols - 1; j++) {
if (board[i][j] == ' ' && (i % 2 != j % 2)) {
return false; // There's at least one empty line
}
}
}
return true;
}
void Board::printBoard() const {
// Function to print the board
// Print the tens and ones places of the column numbers
cout << " "; // Space for row numbers
for (int col = 0; col < cols * 2 - 1; ++col) {
if (!(col % 10)) {
cout << col / 10; // Tens place of column numbers
} else {
cout << " "; // For spaces between dots
}
}
cout << endl;
// Print the ones place of the column numbers
cout << " "; // Space for row numbers
for (int col = 0; col < cols * 2 - 1; ++col) {
cout << (col) % 10; // Print ones place
}
cout << endl;
// Print the board rows with row numbers
for (int row = 0; row < rows * 2 - 1; ++row) {
// Print row number on the left
if (row / 10 > 9) {
if (row % 10 == 0) {
cout << row / 10;
} else {
cout << " ";
}
} else {
if (row % 10 == 0) {
cout << row / 10;
} else {
cout << " ";
}
}
cout << row % 10 << " "; // Print the ones place of the row number
// Print the content of the row (dots, lines, spaces)
for (int col = 0; col < cols * 2 - 1; ++col) {
cout << board[row][col];
}
cout << endl;
}
}
int Board::getRows() const {
return rows;
}
int Board::getCols() const {
return cols;
}

28
board.h Normal file

@ -0,0 +1,28 @@
#ifndef BOARD_H
#define BOARD_H
class Board {
public:
Board(int rows, int cols);
~Board();
char get(int row, int col) const;
void set(int row, int col, char value);
bool isLineValid(int row, int col) const;
void placeLine(int row, int col, char player);
int checkAndMarkBox(int row, int col, char player);
bool isFull() const;
void printBoard() const;
int getRows() const;
int getCols() const;
private:
int rows, cols; // Number of boxes (not dots)
char** board; // Dynamically allocated 2D array
};
#endif // BOARD_H

95
gameplay.cpp Normal file

@ -0,0 +1,95 @@
#include "gameplay.h"
#include <iostream>
#include <fstream>
using namespace std;
Gameplay::Gameplay(int rows, int cols, Player *player1, Player *player2): board(rows, cols), player1(player1), player2(player2) {
}
Gameplay::~Gameplay() {
// TODO: needs revie
free(this->player1);
free(this->player2);
}
void Gameplay::playGame() {
std::ofstream outputFile("output.txt");
if (!outputFile) {
std::cerr << "Error: Could not open output file for writing." << std::endl;
return;
}
// Write board dimensions to the output file
outputFile << board.getRows() << " " << board.getCols() << std::endl;
std::cout << board.getRows() << " " << board.getCols() << std::endl;
char currentPlayer = player1->getName(); // Start with the RandomPlayer
while (true) {
int row, col;
// Determine the move based on the current player
if (currentPlayer == player1->getName()) {
player1->selectLineLocation(board, row, col);
} else {
player2->selectLineLocation(board, row, col);
}
// Validate and place the move
if (board.isLineValid(row, col)) {
board.placeLine(row, col, currentPlayer);
outputFile << currentPlayer << " " << row << " " << col << std::endl;
std::cout << currentPlayer << " " << row << " " << col << std::endl;
int boxesCompleted = board.checkAndMarkBox(row, col, currentPlayer);
if (boxesCompleted == 0) {
// Switch to the next player if no boxes are earned
currentPlayer = (currentPlayer == player1->getName())
? player2->getName()
: player1->getName();
}
} else {
std::cout << currentPlayer << " made an invalid move at (" << row << ", " << col << ")!" << std::endl;
return;
}
// Check if the board is full
if (board.isFull()) {
outputFile << "END" << std::endl;
cout<<"END"<<endl;
break;
}
}
// Print the final board state
board.printBoard();
// Determine the winner and display results
determineWinner();
outputFile.close();
}
void Gameplay::determineWinner() {
int randomPlayerBoxes = 0, strategicPlayerBoxes = 0;
// Iterate over the centers of boxes
for (int r = 1; r < 2 * board.getRows() - 2; r += 2) { // Center rows of boxes
for (int c = 1; c < 2 * board.getCols() - 2; c += 2) { // Center columns of boxes
char boxOwner = board.get(r, c); // Get the owner of the box
if (boxOwner == player1->getName()) {
randomPlayerBoxes++;
} else if (boxOwner == player2->getName()) {
strategicPlayerBoxes++;
}
}
}
// Display results
std::cout << "Player " << player1->getName() << " has " << randomPlayerBoxes << " boxes." << std::endl;
std::cout << "Player " << player2->getName() << " has " << strategicPlayerBoxes << " boxes." << std::endl;
if (randomPlayerBoxes > strategicPlayerBoxes) {
std::cout << "Player " << player1->getName() << " (wins)" << std::endl;
} else if (strategicPlayerBoxes > randomPlayerBoxes) {
std::cout << "Player " << player2->getName() << " (wins)" << std::endl;
} else {
std::cout << "The game is a tie!" << std::endl;
}
}

23
gameplay.h Normal file

@ -0,0 +1,23 @@
#ifndef HW4_GAMEPLAY_H
#define HW4_GAMEPLAY_H
#include "board.h"
#include "random_player.h"
#include "strategic_player.h"
class Gameplay {
private:
Board board;
Player* player1; // Use pointers to enable polymorphism
Player* player2;
void determineWinner();
public:
Gameplay(int rows, int cols, Player* player1, Player* player2);
~Gameplay(); // Destructor to clean up allocated memory
void playGame();
};
#endif // HW4_GAMEPLAY_H

42
main.cpp Normal file

@ -0,0 +1,42 @@
#include "gameplay.h"
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
int main(int argc, char* argv[]) {
int rows, cols;
char player1Name, player2Name;
string player1Type, player2Type;
// Read board dimensions
cin >> rows >> cols;
// Read player 1 details
cin >> player1Name >> player1Type;
// Read player 2 details
cin >> player2Name >> player2Type;
// Determine player types and initialize gameplay
Player* randomPlayerName, *strategicPlayerName;
if (tolower(player1Type[0]) == 'r' && tolower(player2Type[0]) == 's') {
randomPlayerName = new RandomPlayer(player1Name);
strategicPlayerName = new StrategicPlayer(player2Name);
} else if (tolower(player1Type[0]) == 's' && tolower(player2Type[0]) == 'r') {
randomPlayerName = new RandomPlayer(player2Name);
strategicPlayerName = new StrategicPlayer(player1Name);
}else if (tolower(player1Type[0]) == 'r' && tolower(player2Type[0]) == 'r') {
randomPlayerName = new RandomPlayer(player2Name);
strategicPlayerName = new RandomPlayer(player1Name);
} else {
randomPlayerName = new StrategicPlayer(player2Name);
strategicPlayerName = new StrategicPlayer(player1Name);
}
Gameplay game(rows, cols, randomPlayerName, strategicPlayerName);
game.playGame();
return 0;
}

19
player.h Normal file

@ -0,0 +1,19 @@
#ifndef HW4_PLAYER_H
#define HW4_PLAYER_H
#include "board.h"
class Player {
private:
char name;
public:
Player(char name){
this->name = name;
}
virtual void selectLineLocation(Board& board, int& row, int& col) = 0;
virtual char getName() const = 0;
};
#endif //HW4_PLAYER_H

13
random_player.cpp Normal file

@ -0,0 +1,13 @@
#include "random_player.h"
#include <cstdlib>
void RandomPlayer::selectLineLocation(Board& board, int& row, int& col) {
int rows = board.getRows();
int cols = board.getCols();
// Loop until a valid move is found
do {
row = rand() % (2 * rows - 1); // Random row within the board dimensions
col = rand() % (2 * cols - 1); // Random column within the board dimensions
} while (!board.isLineValid(row, col)); // Check if the move is valid
}

25
random_player.h Normal file

@ -0,0 +1,25 @@
#ifndef HW4_RANDOM_PLAYER_H
#define HW4_RANDOM_PLAYER_H
#include "board.h"
#include "player.h"
#include <cstdlib>
#include <ctime>
class RandomPlayer: public Player {
private:
char name;
public:
RandomPlayer(char name): Player(name) {
srand(static_cast<unsigned int>(time(nullptr)));
this->name = name;
}
void selectLineLocation(Board& board, int& row, int& col);
char getName() const override {
return this->name;
}
};
#endif //HW4_RANDOM_PLAYER_H

96
strategic_player.cpp Normal file

@ -0,0 +1,96 @@
#include "strategic_player.h"
using namespace std;
void StrategicPlayer::selectLineLocation(Board& board, int& row, int& col) {
int rows = board.getRows();
int cols = board.getCols();
// 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 (board.isLineValid(r - 1, c) && // Top line
board.get(r + 1, c) != ' ' && // Bottom line
board.get(r, c - 1) != ' ' && // Left line
board.get(r, c + 1) != ' ') { // Right line
row = r - 1;
col = c;
return;
}
if (board.isLineValid(r + 1, c) && // Bottom line
board.get(r - 1, c) != ' ' && // Top line
board.get(r, c - 1) != ' ' && // Left line
board.get(r, c + 1) != ' ') { // Right line
row = r + 1;
col = c;
return;
}
if (board.isLineValid(r, c - 1) && // Left line
board.get(r - 1, c) != ' ' && // Top line
board.get(r + 1, c) != ' ' && // Bottom line
board.get(r, c + 1) != ' ') { // Right line
row = r;
col = c - 1;
return;
}
if (board.isLineValid(r, c + 1) && // Right line
board.get(r - 1, c) != ' ' && // Top line
board.get(r + 1, c) != ' ' && // Bottom line
board.get(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 (board.isLineValid(r, c)) {
// Simulate placing the line
board.placeLine(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 (board.get(nr, nc) == ' ') {
int adjacentLines = 0;
if (nr > 0 && board.get(nr - 1, nc) != ' ') adjacentLines++; // Top line
if (nr < 2 * rows - 2 && board.get(nr + 1, nc) != ' ') adjacentLines++; // Bottom line
if (nc > 0 && board.get(nr, nc - 1) != ' ') adjacentLines++; // Left line
if (nc < 2 * cols - 2 && board.get(nr, nc + 1) != ' ') adjacentLines++; // Right line
if (adjacentLines == 3) { // Opponent can complete this box
createsOpportunity = true;
break;
}
}
}
if (createsOpportunity) break;
}
board.set(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 (board.isLineValid(r, c)) {
row = r;
col = c;
return;
}
}
}
}

26
strategic_player.h Normal file

@ -0,0 +1,26 @@
//
// Created by sapan on 18-11-2024.
//
#ifndef HW4_STRATEGIC_PLAYER_H
#define HW4_STRATEGIC_PLAYER_H
#include "board.h"
#include "player.h"
class StrategicPlayer: public Player {
private:
char name;
public:
StrategicPlayer(char name): Player(name) {
this->name = name;
}
void selectLineLocation(Board& board, int& row, int& col);
char getName() const override {
return this->name;
}
};
#endif //HW4_STRATEGIC_PLAYER_H