mirror of
https://github.com/caperren/school_archives.git
synced 2025-11-09 21:51:15 +00:00
Added missing classes from final year at OSU
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
To run, use...
|
||||
|
||||
python3 othello.py human minimax
|
||||
|
||||
You must use python3!
|
||||
@@ -0,0 +1,106 @@
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
#include "Board.h"
|
||||
|
||||
void Board::delete_grid() {
|
||||
for (int c = 0; c < num_cols; c++) {
|
||||
delete[] grid[c];
|
||||
}
|
||||
delete[] grid;
|
||||
}
|
||||
|
||||
Board::Board(int cols, int rows) :
|
||||
num_cols(cols), num_rows(rows) {
|
||||
grid = new char*[num_cols];
|
||||
for (int c = 0; c < num_cols; c++) {
|
||||
grid[c] = new char[num_rows];
|
||||
for (int r = 0; r < num_rows; r++) {
|
||||
grid[c][r] = EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Board::Board(const Board& other) {
|
||||
num_cols = other.num_cols;
|
||||
num_rows = other.num_rows;
|
||||
grid = new char*[num_cols];
|
||||
for (int c = 0; c < num_cols; c++) {
|
||||
grid[c] = new char[num_rows];
|
||||
for (int r = 0; r < num_rows; r++) {
|
||||
grid[c][r] = other.grid[c][r];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Board& Board::operator=(const Board& rhs) {
|
||||
if (this == &rhs) {
|
||||
return *this;
|
||||
} else {
|
||||
num_cols = rhs.num_cols;
|
||||
num_rows = rhs.num_rows;
|
||||
if (grid != NULL) {
|
||||
delete_grid();
|
||||
}
|
||||
grid = new char*[num_cols];
|
||||
for (int c = 0; c < num_cols; c++) {
|
||||
grid[c] = new char[num_rows];
|
||||
for (int r = 0; r < num_rows; r++) {
|
||||
grid[c][r] = rhs.grid[c][r];
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
Board::~Board() {
|
||||
delete_grid();
|
||||
}
|
||||
|
||||
char Board::get_cell(int col, int row) const {
|
||||
assert((col >= 0) && (col < num_cols));
|
||||
assert((row >= 0) && (row < num_rows));
|
||||
return grid[col][row];
|
||||
}
|
||||
|
||||
void Board::set_cell(int col, int row, char val) {
|
||||
assert((col >= 0) && (col < num_cols));
|
||||
assert((row >= 0) && (row < num_rows));
|
||||
grid[col][row] = val;
|
||||
}
|
||||
|
||||
bool Board::is_cell_empty(int col, int row) const {
|
||||
if (grid[col][row] == EMPTY) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Board::is_in_bounds(int col, int row) const {
|
||||
if ((col >= 0) && (col < num_cols) && (row >= 0) && (row < num_rows)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Board::display() const {
|
||||
for (int r = get_num_rows() - 1; r >= 0; r--) {
|
||||
std::cout << r << ":| ";
|
||||
for (int c = 0; c < get_num_cols(); c++) {
|
||||
std::cout << get_cell(c, r) << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << " -";
|
||||
for (int c = 0; c < get_num_cols(); c++) {
|
||||
std::cout << "--";
|
||||
}
|
||||
std::cout << "\n";
|
||||
std::cout << " ";
|
||||
for (int c = 0; c < get_num_cols(); c++) {
|
||||
std::cout << c << " ";
|
||||
}
|
||||
std::cout << "\n\n";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Board class
|
||||
*/
|
||||
|
||||
#ifndef BOARD_H
|
||||
#define BOARD_H
|
||||
|
||||
#define EMPTY '.'
|
||||
|
||||
/** Directions enum for the Othello Board */
|
||||
enum Direction {N,NE,E,SE,S,SW,W,NW};
|
||||
|
||||
/**
|
||||
* This is a generic board class that serves as a wrapper for a 2D array.
|
||||
* It will be used for board games like Othello, Tic-Tac-Toe and Connect 4.
|
||||
*/
|
||||
class Board {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @param cols The number of columns in the board
|
||||
* @param rows The number of rows in the board
|
||||
* Constructor that creates a 2D board with the given number of columns
|
||||
* and rows
|
||||
*/
|
||||
Board(int cols, int rows);
|
||||
|
||||
/**
|
||||
* @param other A reference to the Board object being copied
|
||||
* This is the copy constructor for the Board class
|
||||
*/
|
||||
Board(const Board& other);
|
||||
|
||||
/**
|
||||
* Destructor for the Board class
|
||||
*/
|
||||
~Board();
|
||||
|
||||
/**
|
||||
* @param rhs The "right-hand side" for the assignment ie. the object
|
||||
* you are copying from.
|
||||
* @return Returns a reference to the "left-hand side" of the assignment ie.
|
||||
* the object the values are assigned to
|
||||
* Overloaded assignment operator for the Board class
|
||||
*/
|
||||
Board& operator=(const Board& rhs);
|
||||
|
||||
/**
|
||||
* @return Returns the number of rows in the board
|
||||
* An accessor that gets the number of rows in the board
|
||||
*/
|
||||
int get_num_rows() const { return num_rows; }
|
||||
|
||||
/**
|
||||
* @return Returns the number of columns in the board
|
||||
* An accessor to get the number of columns in the board
|
||||
*/
|
||||
int get_num_cols() const { return num_cols; }
|
||||
|
||||
/**
|
||||
* @param col The column of the cell you want to retrieve
|
||||
* @param row The row of the cell you want to retrieve
|
||||
* @return Returns the character at the specified cell
|
||||
* Returns the character at the specified column and row
|
||||
*/
|
||||
char get_cell(int col, int row) const;
|
||||
|
||||
/**
|
||||
* @param col The column of the cell you want to set
|
||||
* @param row The row of the cell you want to set
|
||||
* @param val The value you want to set the cell to
|
||||
* Sets the cell at the given row and column to the specified value
|
||||
*/
|
||||
void set_cell(int col, int row, char val);
|
||||
|
||||
/**
|
||||
* @param col The column of the cell you are checking
|
||||
* @param row The row of the cell you are checking
|
||||
* @return true if the cell is empty and false otherwise
|
||||
*/
|
||||
bool is_cell_empty(int col, int row) const;
|
||||
|
||||
/**
|
||||
* @param col The column value for the in-bounds check
|
||||
* @param row The row value for the in-bounds check
|
||||
* @return true if the column is >= 0 and < num_cols and if the row is >= 0 and < num_rows. Returns false otherwise.
|
||||
*/
|
||||
bool is_in_bounds(int col, int row) const;
|
||||
|
||||
/**
|
||||
* Prints the board to screen. Should probably have overloaded >> but oh well.
|
||||
*/
|
||||
void display() const;
|
||||
|
||||
protected:
|
||||
|
||||
/** The number of rows in the board */
|
||||
int num_rows;
|
||||
|
||||
/** The number of columns in the board */
|
||||
int num_cols;
|
||||
|
||||
/** A 2D array of chars representing the board */
|
||||
char** grid;
|
||||
|
||||
/**
|
||||
* Deletes the 2D array.
|
||||
*/
|
||||
void delete_grid();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,137 @@
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
#include "GameDriver.h"
|
||||
|
||||
GameDriver::GameDriver(char* p1type, char* p2type, int num_rows, int num_cols) {
|
||||
if( strcmp(p1type,"human") == 0 ) {
|
||||
p1 = new HumanPlayer('X');
|
||||
} else if( strcmp(p1type,"minimax") == 0 ) {
|
||||
p1 = new MinimaxPlayer('X');
|
||||
} else {
|
||||
std::cout << "Invalid type of player for player 1" << std::endl;
|
||||
}
|
||||
|
||||
if( strcmp(p2type,"human") == 0 ) {
|
||||
p2 = new HumanPlayer('O');
|
||||
} else if( strcmp(p2type,"minimax") == 0 ) {
|
||||
p2 = new MinimaxPlayer('O');
|
||||
} else {
|
||||
std::cout << "Invalid type of player for player 2" << std::endl;
|
||||
}
|
||||
|
||||
board = new OthelloBoard(num_rows, num_cols,p1->get_symbol(), p2->get_symbol());
|
||||
board->initialize();
|
||||
}
|
||||
|
||||
GameDriver::GameDriver(const GameDriver& other) {
|
||||
board = new OthelloBoard(*(other.board));
|
||||
p1 = other.p1->clone();
|
||||
p2 = other.p2->clone();
|
||||
}
|
||||
|
||||
GameDriver& GameDriver::operator=(const GameDriver& rhs) {
|
||||
if (this == &rhs) {
|
||||
return *this;
|
||||
} else {
|
||||
if( board != NULL ) {
|
||||
delete board;
|
||||
}
|
||||
board = new OthelloBoard(*(rhs.board));
|
||||
if( p1 != NULL ) {
|
||||
delete p1;
|
||||
p1 = rhs.p1->clone();
|
||||
}
|
||||
if( p2 != NULL ) {
|
||||
delete p2;
|
||||
p2 = rhs.p2->clone();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
GameDriver::~GameDriver() {
|
||||
delete board;
|
||||
delete p1;
|
||||
delete p2;
|
||||
}
|
||||
|
||||
void GameDriver::display() {
|
||||
std::cout << std::endl << "Player 1 (" << p1->get_symbol() << ") score: "
|
||||
<< board->count_score(p1->get_symbol()) << std::endl;
|
||||
std::cout << "Player 2 (" << p2->get_symbol() << ") score: "
|
||||
<< board->count_score(p2->get_symbol()) << std::endl << std::endl;
|
||||
|
||||
board->display();
|
||||
}
|
||||
|
||||
void GameDriver::process_move(Player* curr_player, Player* opponent) {
|
||||
int col = -1;
|
||||
int row = -1;
|
||||
bool invalid_move = true;
|
||||
while (invalid_move) {
|
||||
curr_player->get_move(board, col, row);
|
||||
if (!board->is_legal_move(col, row, curr_player->get_symbol())) {
|
||||
std::cout << "Invalid move.\n";
|
||||
continue;
|
||||
} else {
|
||||
std::cout << "Selected move: col = " << col << ", row = " << row << std::endl;
|
||||
board->play_move(col,row,curr_player->get_symbol());
|
||||
invalid_move = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameDriver::run() {
|
||||
int toggle = 0;
|
||||
int cant_move_counter=0;
|
||||
Player* current = p1;
|
||||
Player* opponent = p2;
|
||||
|
||||
display();
|
||||
std::cout << "Player 1 (" << p1->get_symbol() << ") move:\n";
|
||||
while (1) {
|
||||
if( board->has_legal_moves_remaining(current->get_symbol())) {
|
||||
cant_move_counter = 0;
|
||||
process_move(current, opponent);
|
||||
display();
|
||||
} else {
|
||||
std::cout << "Can't move\n";
|
||||
if( cant_move_counter == 1 ) {
|
||||
// Both players can't move, game over
|
||||
break;
|
||||
} else {
|
||||
cant_move_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
toggle = (toggle + 1) % 2;
|
||||
|
||||
if (toggle == 0) {
|
||||
current = p1;
|
||||
opponent = p2;
|
||||
std::cout << "Player 1 (" << p1->get_symbol() << ") move:\n";
|
||||
} else {
|
||||
current = p2;
|
||||
opponent = p1;
|
||||
std::cout << "Player 2 (" << p2->get_symbol() << ") move:\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ( board->count_score(p1->get_symbol()) == board->count_score(p2->get_symbol())) {
|
||||
std::cout << "Tie game" << std::endl;
|
||||
} else if ( board->count_score(p1->get_symbol()) > board->count_score(p2->get_symbol())) {
|
||||
std::cout << "Player 1 wins" << std::endl;
|
||||
} else {
|
||||
std::cout << "Player 2 wins" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if( argc != 3 ) {
|
||||
std::cout << "Usage: othello <player1 type> <player2 type>" << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
GameDriver* game = new GameDriver(argv[1],argv[2],4,4);
|
||||
game->run();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#ifndef GAMEDRIVER_H
|
||||
#define GAMEDRIVER_H
|
||||
|
||||
#include "OthelloBoard.h"
|
||||
#include "Player.h"
|
||||
#include "HumanPlayer.h"
|
||||
#include "MinimaxPlayer.h"
|
||||
|
||||
/**
|
||||
* This class represents the main driver for the game. The driver controls the turn-based behavior
|
||||
* of the game.
|
||||
*/
|
||||
class GameDriver {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @param p1type A string (human or minimax) describing the type of Player1
|
||||
* @param p2type A string (human or minimax) describing the type of Player2
|
||||
* @param num_rows The number of rows in Othello
|
||||
* @param num_cols The number of columns in Othello
|
||||
* This is the constructor for the GameDriver
|
||||
*/
|
||||
GameDriver(char* p1type, char* p2type, int num_rows, int num_cols);
|
||||
|
||||
/**
|
||||
* @param other The GameDriver object you are copying from
|
||||
* Copy constructor for the GameDriver class
|
||||
*/
|
||||
GameDriver(const GameDriver& other);
|
||||
|
||||
/**
|
||||
* @param rhs The right-hand side of the assignment
|
||||
* @return The left-hand side of the assignment
|
||||
* Overloaded assignment operator for the GameDriver class.
|
||||
*/
|
||||
GameDriver& operator=(const GameDriver& rhs);
|
||||
|
||||
/**
|
||||
* Destructor for the GameDriver class
|
||||
*/
|
||||
virtual ~GameDriver();
|
||||
|
||||
/**
|
||||
* Runs the game and keeps track of the turns.
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* Displays the game.
|
||||
*/
|
||||
void display();
|
||||
|
||||
private:
|
||||
|
||||
/** Internal Othello board object */
|
||||
OthelloBoard* board;
|
||||
|
||||
/** Player 1 object */
|
||||
Player* p1;
|
||||
|
||||
/** Player 2 object */
|
||||
Player* p2;
|
||||
|
||||
/**
|
||||
* @param curr_player A pointer to the player that has the current move
|
||||
* @param opponent A pointer to the opponent for the player that has the current move
|
||||
* Handles actually making a move in the game.
|
||||
*/
|
||||
void process_move(Player* curr_player, Player* opponent);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <iostream>
|
||||
#include "HumanPlayer.h"
|
||||
|
||||
HumanPlayer::HumanPlayer(char symb) : Player(symb) {
|
||||
|
||||
}
|
||||
|
||||
HumanPlayer::~HumanPlayer() {
|
||||
|
||||
}
|
||||
|
||||
void HumanPlayer::get_move(OthelloBoard* b, int& col, int& row) {
|
||||
std::cout << "Enter col: ";
|
||||
std::cin >> col;
|
||||
std::cout << "Enter row: ";
|
||||
std::cin >> row;
|
||||
}
|
||||
|
||||
HumanPlayer* HumanPlayer::clone() {
|
||||
HumanPlayer *result = new HumanPlayer(symbol);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef HUMAN_PLAYER
|
||||
#define HUMAN_PLAYER
|
||||
|
||||
#include "Player.h"
|
||||
#include "OthelloBoard.h"
|
||||
|
||||
/**
|
||||
* This class represents a human player
|
||||
*/
|
||||
class HumanPlayer : public Player {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @symb The symbol used for the human player's pieces
|
||||
* The constructor for the HumanPlayer class
|
||||
*/
|
||||
HumanPlayer(char symb);
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
virtual ~HumanPlayer();
|
||||
|
||||
/**
|
||||
* @param b The current board for the game.
|
||||
* @param col Holds the return value for the column of the move
|
||||
* @param row Holds the return value for the row of the move
|
||||
* Obtains the (col,row) coordinates for the current move
|
||||
*/
|
||||
void get_move(OthelloBoard* b, int& col, int& row);
|
||||
|
||||
/**
|
||||
* @return A pointer to a copy of the HumanPlayer object
|
||||
* This is a virtual copy constructor
|
||||
*/
|
||||
HumanPlayer *clone();
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
CXX = g++
|
||||
CXXFLAGS = -std=c++0x
|
||||
SRCS = Board.cpp OthelloBoard.cpp Player.cpp HumanPlayer.cpp GameDriver.cpp MinimaxPlayer.cpp
|
||||
HEADERS = Board.h OthelloBoard.h Player.h HumanPlayer.h GameDriver.h MinimaxPlayer.h
|
||||
OBJS = Board.o OthelloBoard.o Player.o HumanPlayer.o GameDriver.o MinimaxPlayer.o
|
||||
|
||||
all: ${SRCS} ${HEADERS}
|
||||
${CXX} ${CXXFLAGS} ${SRCS} -o othello
|
||||
|
||||
${OBJS}: ${SRCS}
|
||||
${CXX} -c $(@:.o=.cpp)
|
||||
|
||||
clean:
|
||||
rm -f *.o othello
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* MinimaxPlayer.cpp
|
||||
*
|
||||
* Created on: Apr 17, 2015
|
||||
* Author: wong
|
||||
*/
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
#include "MinimaxPlayer.h"
|
||||
|
||||
using std::vector;
|
||||
|
||||
MinimaxPlayer::MinimaxPlayer(char symb) :
|
||||
Player(symb) {
|
||||
|
||||
}
|
||||
|
||||
MinimaxPlayer::~MinimaxPlayer() {
|
||||
|
||||
}
|
||||
|
||||
void MinimaxPlayer::get_move(OthelloBoard* b, int& col, int& row) {
|
||||
// To be filled in by you
|
||||
}
|
||||
|
||||
MinimaxPlayer* MinimaxPlayer::clone() {
|
||||
MinimaxPlayer* result = new MinimaxPlayer(symbol);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* MinimaxPlayer.h
|
||||
*
|
||||
* Created on: Apr 17, 2015
|
||||
* Author: wong
|
||||
*/
|
||||
|
||||
#ifndef MINIMAXPLAYER_H
|
||||
#define MINIMAXPLAYER_H
|
||||
|
||||
#include "OthelloBoard.h"
|
||||
#include "Player.h"
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* This class represents an AI player that uses the Minimax algorithm to play the game
|
||||
* intelligently.
|
||||
*/
|
||||
class MinimaxPlayer : public Player {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @param symb This is the symbol for the minimax player's pieces
|
||||
*/
|
||||
MinimaxPlayer(char symb);
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
virtual ~MinimaxPlayer();
|
||||
|
||||
/**
|
||||
* @param b The board object for the current state of the board
|
||||
* @param col Holds the return value for the column of the move
|
||||
* @param row Holds the return value for the row of the move
|
||||
*/
|
||||
void get_move(OthelloBoard* b, int& col, int& row);
|
||||
|
||||
/**
|
||||
* @return A copy of the MinimaxPlayer object
|
||||
* This is a virtual copy constructor
|
||||
*/
|
||||
MinimaxPlayer* clone();
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* OthelloBoard.cpp
|
||||
*
|
||||
* Created on: Apr 18, 2015
|
||||
* Author: wong
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include "OthelloBoard.h"
|
||||
|
||||
OthelloBoard::OthelloBoard(int cols, int rows, char p1_symb, char p2_symb) :
|
||||
Board(cols, rows), p1_symbol(p1_symb), p2_symbol(p2_symb) {
|
||||
|
||||
}
|
||||
|
||||
OthelloBoard::OthelloBoard(const OthelloBoard& other) :
|
||||
Board(other), p1_symbol(other.p1_symbol), p2_symbol(other.p2_symbol) {
|
||||
|
||||
}
|
||||
|
||||
OthelloBoard::~OthelloBoard() {
|
||||
|
||||
}
|
||||
|
||||
void OthelloBoard::initialize() {
|
||||
set_cell(num_cols / 2 - 1, num_rows / 2 - 1, p1_symbol);
|
||||
set_cell(num_cols / 2, num_rows / 2, p1_symbol);
|
||||
set_cell(num_cols / 2 - 1, num_rows / 2, p2_symbol);
|
||||
set_cell(num_cols / 2, num_rows / 2 - 1, p2_symbol);
|
||||
|
||||
|
||||
}
|
||||
|
||||
OthelloBoard& OthelloBoard::operator=(const OthelloBoard& rhs) {
|
||||
Board::operator=(rhs);
|
||||
p1_symbol = rhs.p1_symbol;
|
||||
p2_symbol = rhs.p2_symbol;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void OthelloBoard::set_coords_in_direction(int col, int row, int& next_col,
|
||||
int& next_row, int dir) const {
|
||||
switch (dir) {
|
||||
case N:
|
||||
next_col = col;
|
||||
next_row = row + 1;
|
||||
break;
|
||||
case NE:
|
||||
next_col = col + 1;
|
||||
next_row = row + 1;
|
||||
break;
|
||||
case E:
|
||||
next_col = col + 1;
|
||||
next_row = row;
|
||||
break;
|
||||
case SE:
|
||||
next_col = col + 1;
|
||||
next_row = row - 1;
|
||||
break;
|
||||
case S:
|
||||
next_col = col;
|
||||
next_row = row - 1;
|
||||
break;
|
||||
case SW:
|
||||
next_col = col - 1;
|
||||
next_row = row - 1;
|
||||
break;
|
||||
case W:
|
||||
next_col = col - 1;
|
||||
next_row = row;
|
||||
break;
|
||||
case NW:
|
||||
next_col = col - 1;
|
||||
next_row = row + 1;
|
||||
break;
|
||||
default:
|
||||
assert("Invalid direction");
|
||||
}
|
||||
}
|
||||
|
||||
bool OthelloBoard::check_endpoint(int col, int row, char symbol, int dir,
|
||||
bool match_symbol) const {
|
||||
int next_row = -1;
|
||||
int next_col = -1;
|
||||
|
||||
if (!is_in_bounds(col, row) || is_cell_empty(col, row)) {
|
||||
return false;
|
||||
} else {
|
||||
if (match_symbol) {
|
||||
if (get_cell(col, row) == symbol) {
|
||||
return true;
|
||||
} else {
|
||||
set_coords_in_direction(col, row, next_col, next_row, dir);
|
||||
return check_endpoint(next_col, next_row, symbol, dir,
|
||||
match_symbol);
|
||||
}
|
||||
} else {
|
||||
if (get_cell(col, row) == symbol) {
|
||||
return false;
|
||||
} else {
|
||||
set_coords_in_direction(col, row, next_col, next_row, dir);
|
||||
return check_endpoint(next_col, next_row, symbol, dir,
|
||||
!match_symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OthelloBoard::is_legal_move(int col, int row, char symbol) const {
|
||||
bool result = false;
|
||||
int next_row = -1;
|
||||
int next_col = -1;
|
||||
if (!is_in_bounds(col, row) || !is_cell_empty(col, row)) {
|
||||
return result;
|
||||
}
|
||||
for (int d = N; d <= NW; d++) {
|
||||
set_coords_in_direction(col, row, next_col, next_row, d);
|
||||
if (check_endpoint(next_col, next_row, symbol, d, false)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int OthelloBoard::flip_pieces_helper(int col, int row, char symbol, int dir) {
|
||||
int next_row = -1;
|
||||
int next_col = -1;
|
||||
|
||||
if (get_cell(col, row) == symbol) {
|
||||
return 0;
|
||||
} else {
|
||||
set_cell(col, row, symbol);
|
||||
set_coords_in_direction(col, row, next_col, next_row, dir);
|
||||
return 1 + flip_pieces_helper(next_col, next_row, symbol, dir);
|
||||
}
|
||||
}
|
||||
|
||||
int OthelloBoard::flip_pieces(int col, int row, char symbol) {
|
||||
int pieces_flipped = 0;
|
||||
int next_row = -1;
|
||||
int next_col = -1;
|
||||
|
||||
assert(is_in_bounds(col, row));
|
||||
for (int d = N; d <= NW; d++) {
|
||||
set_coords_in_direction(col, row, next_col, next_row, d);
|
||||
if (check_endpoint(next_col, next_row, symbol, d, false)) {
|
||||
pieces_flipped += flip_pieces_helper(next_col, next_row, symbol, d);
|
||||
}
|
||||
}
|
||||
return pieces_flipped;
|
||||
}
|
||||
|
||||
bool OthelloBoard::has_legal_moves_remaining(char symbol) const {
|
||||
for (int c = 0; c < num_cols; c++) {
|
||||
for (int r = 0; r < num_rows; r++) {
|
||||
if (is_cell_empty(c, r) && is_legal_move(c, r, symbol)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int OthelloBoard::count_score(char symbol) const {
|
||||
int score = 0;
|
||||
for (int c = 0; c < num_cols; c++) {
|
||||
for (int r = 0; r < num_rows; r++) {
|
||||
if (grid[c][r] == symbol) {
|
||||
score++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
void OthelloBoard::play_move(int col, int row, char symbol) {
|
||||
set_cell(col, row, symbol);
|
||||
flip_pieces(col, row, symbol);
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* OthelloBoard.h
|
||||
*
|
||||
* Created on: Apr 18, 2015
|
||||
* Author: wong
|
||||
*/
|
||||
|
||||
#ifndef OTHELLOBOARD_H_
|
||||
#define OTHELLOBOARD_H_
|
||||
|
||||
#include "Board.h"
|
||||
|
||||
/**
|
||||
* This class is a specialized version of the Board class for Othello. The OthelloBoard
|
||||
* class respects the rules of Othello and also keeps track of the symbols for Player
|
||||
* 1 and Player 2.
|
||||
*/
|
||||
class OthelloBoard : public Board {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @cols The number of columns in the game of Othello
|
||||
* @rows The number of rows in the game of Othello
|
||||
* @p1_symbol The symbol used for Player 1's pieces on the board
|
||||
* @p2_symbol The symbol used for Player 2's pieces on the board
|
||||
* This is a constructor for an OthelloBoard clas.
|
||||
*/
|
||||
OthelloBoard(int cols, int rows, char p1_symbol, char p2_symbol);
|
||||
|
||||
/**
|
||||
* @param other The OthelloBoard object you are copying from.
|
||||
* This is the copy constructor for the OthelloBoard class.
|
||||
*/
|
||||
OthelloBoard(const OthelloBoard& other);
|
||||
|
||||
/**
|
||||
* The destructor for the OthelloBoard class.
|
||||
*/
|
||||
virtual ~OthelloBoard();
|
||||
|
||||
/**
|
||||
* Initializes the Othello board to the starting position of the pieces
|
||||
* for Players 1 and 2
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* @param rhs The right-hand side object of the assignment
|
||||
* @return The left-hand side object of the assignment
|
||||
* This is the overloaded assignment operator for the OthelloBoard class
|
||||
*/
|
||||
OthelloBoard& operator=(const OthelloBoard& rhs);
|
||||
|
||||
/**
|
||||
* @param col The column for where your piece goes
|
||||
* @param row The row for where your piece goes
|
||||
* @return true if the move is legal, false otherwise.
|
||||
* Checks the legality of a move that places a piece at the specified col and
|
||||
* row.
|
||||
*/
|
||||
bool is_legal_move(int col, int row, char symbol) const;
|
||||
|
||||
/**
|
||||
* @param symbol This is the symbol for the current player.
|
||||
* @param col The column for where your piece goes
|
||||
* @param row The row for where your piece goes
|
||||
* Flips the in-between pieces once you put down a piece the specified
|
||||
* col and row position. The symbol argument specifies who the
|
||||
* current move belongs to.
|
||||
*/
|
||||
int flip_pieces(int col, int row, char symbol);
|
||||
|
||||
/**
|
||||
* @param symbol This symbol specifies the symbol for the current player (i.e.
|
||||
* who the current move belongs to)
|
||||
* @return true if there are still moves remaining, false otherwise
|
||||
* Checks if the game is over.
|
||||
*/
|
||||
bool has_legal_moves_remaining(char symbol) const;
|
||||
|
||||
/**
|
||||
* @param symbol The symbol representing a particular player.
|
||||
* Returns the score for the player with the specified symbol.
|
||||
*/
|
||||
int count_score(char symbol) const;
|
||||
|
||||
/**
|
||||
* @param col The column where the piece goes
|
||||
* @param row The row where the piece goes
|
||||
* Plays the move by placing a piece, with the given symbol, down at the specified
|
||||
* col and row. Then, any pieces sandwiched in between the two endpoints are flipped.
|
||||
*/
|
||||
void play_move(int col, int row, char symbol);
|
||||
|
||||
/**
|
||||
* @return Returns the symbol for Player 1 (the maximizing player)'s pieces
|
||||
* Returns the symbol for Player 1's pieces
|
||||
*/
|
||||
char get_p1_symbol() { return p1_symbol; }
|
||||
|
||||
/**
|
||||
* @return Returns the symbol for Player 2 (the minimizing player)'s pieces
|
||||
* Returns the symbol for Player 2's pieces
|
||||
*/
|
||||
char get_p2_symbol() { return p2_symbol; }
|
||||
|
||||
private:
|
||||
|
||||
/** The symbol for Player 1's pieces */
|
||||
char p1_symbol;
|
||||
|
||||
/** The symbol for Player 2's pieces */
|
||||
char p2_symbol;
|
||||
|
||||
/**
|
||||
* @param col The column of the starting point
|
||||
* @param row The row of the starting point
|
||||
* @param next_col The return value for the column
|
||||
* @param next_row The return value for the row
|
||||
* @param dir The direction you want to move
|
||||
* Sets the coordinates of next_col and next_row to be the coordinates if you were
|
||||
* moving in the direction specified by the argument dir starting at position (col,row)
|
||||
*/
|
||||
void set_coords_in_direction(int col, int row, int& next_col, int& next_row, int dir) const;
|
||||
|
||||
/**
|
||||
* @param col The column of the starting point
|
||||
* @param row The row of the starting point
|
||||
* @param symbol The symbol of the current player. You will match (or not match) this symbol
|
||||
* at the endpoint
|
||||
* @param dir The direction you are moving in
|
||||
* @param match_symbol If true, it will return true if the arg symbol matches the endpoint. If false,
|
||||
* it will return true if the arg symbol doesn't match the endpoint.
|
||||
* If you start at (col,row) and move in direction dir, this function will check the endpoint
|
||||
* of a trail of pieces. If match_symbol is true, it will return true if the endpoint matches
|
||||
* the argument symbol (and false otherwise). If match_symbol is false, it will return true
|
||||
* if the endpoint doesn't match the argument symbol (and false otherwise).
|
||||
*/
|
||||
bool check_endpoint(int col, int row, char symbol, int dir,
|
||||
bool match_symbol) const;
|
||||
|
||||
/**
|
||||
* @param col The column of the starting point
|
||||
* @param row The row of the starting point
|
||||
* @param symbol This is the symbol at the endpoint that terminates the row of pieces flipped
|
||||
* @param dir The direction you are moving
|
||||
* This is a helper function for the recursive flip_pieces function.
|
||||
*/
|
||||
int flip_pieces_helper(int col, int row, char symbol, int dir);
|
||||
};
|
||||
|
||||
#endif /* OTHELLOBOARD_H_ */
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "Player.h"
|
||||
|
||||
Player::Player(char symb) : symbol(symb) {
|
||||
|
||||
}
|
||||
|
||||
Player::~Player() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Player class
|
||||
*/
|
||||
|
||||
#ifndef PLAYER_H
|
||||
#define PLAYER_H
|
||||
|
||||
#include "OthelloBoard.h"
|
||||
|
||||
/**
|
||||
* This is an abstract base class for a Player
|
||||
*/
|
||||
class Player {
|
||||
public:
|
||||
/**
|
||||
* @param symb The symbol for the player's pieces
|
||||
*/
|
||||
Player(char symb);
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
virtual ~Player();
|
||||
|
||||
/**
|
||||
* @return The player's symbol
|
||||
* Gets the symbol for the player's pieces
|
||||
*/
|
||||
char get_symbol() { return symbol; }
|
||||
|
||||
/**
|
||||
* @param b The current board
|
||||
* @param col Holds the column of the player's move
|
||||
* @param row Holds the row of the player's move
|
||||
* Gets the next move for the player
|
||||
*/
|
||||
virtual void get_move(OthelloBoard* b, int& col, int& row) = 0;
|
||||
|
||||
/**
|
||||
* @return A copy of the Player object
|
||||
* Virtual copy constructor
|
||||
*/
|
||||
virtual Player* clone() = 0;
|
||||
protected:
|
||||
|
||||
/** The symbol for the player's pieces*/
|
||||
char symbol;
|
||||
};
|
||||
|
||||
#endif
|
||||
Binary file not shown.
@@ -0,0 +1,328 @@
|
||||
import argparse
|
||||
import math
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class Player:
|
||||
def __init__(self, player_number, board_symbol):
|
||||
self._player_number = player_number
|
||||
self._board_symbol = board_symbol
|
||||
|
||||
@property
|
||||
def board_symbol(self):
|
||||
return self._board_symbol
|
||||
|
||||
@property
|
||||
def player_number(self):
|
||||
return self._player_number
|
||||
|
||||
def get_move(self, board):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class HumanPlayer(Player):
|
||||
def __init__(self, player_number, board_symbol):
|
||||
super(HumanPlayer, self).__init__(player_number, board_symbol)
|
||||
|
||||
def get_move(self, _):
|
||||
valid_input = False
|
||||
column = None
|
||||
row = None
|
||||
|
||||
while not valid_input:
|
||||
try:
|
||||
column = int(input("Enter Column: "))
|
||||
row = int(input("Enter Row: "))
|
||||
valid_input = True
|
||||
except ValueError:
|
||||
print("Value entered was not an integer, please input column and row again...")
|
||||
|
||||
return column, row
|
||||
|
||||
|
||||
class MinimaxPlayer(Player):
|
||||
def __init__(self, player_number, board_symbol):
|
||||
super(MinimaxPlayer, self).__init__(player_number, board_symbol)
|
||||
|
||||
def utility(self, board, player_symbol):
|
||||
if player_symbol == "X":
|
||||
return board.count_score("O") - board.count_score("X")
|
||||
else:
|
||||
return board.count_score("X") - board.count_score("O")
|
||||
|
||||
def successors(self, board, player_symbol):
|
||||
successors = []
|
||||
|
||||
for row in range(board.num_rows):
|
||||
for column in range(board.num_columns):
|
||||
if board.cell_empty(column, row):
|
||||
if board.is_legal_move(column, row, player_symbol):
|
||||
successors.append((column, row))
|
||||
|
||||
return successors
|
||||
|
||||
def minimax_max_value(self, board, current_player_symbol):
|
||||
if not board.has_legal_moves_remaining(current_player_symbol):
|
||||
return self.utility(board, current_player_symbol), (None, None)
|
||||
|
||||
best_found_value = -math.inf, (None, None)
|
||||
|
||||
for successor in self.successors(board, current_player_symbol):
|
||||
new_board = deepcopy(board)
|
||||
new_board.play_move(*successor, current_player_symbol)
|
||||
|
||||
next_player_symbol = "O" if current_player_symbol == "X" else "X"
|
||||
|
||||
new_board_minimax = self.minimax_min_value(new_board, next_player_symbol)
|
||||
|
||||
if new_board_minimax[0] > best_found_value[0]:
|
||||
best_found_value = new_board_minimax[0], successor
|
||||
|
||||
return best_found_value
|
||||
|
||||
def minimax_min_value(self, board, current_player_symbol):
|
||||
if not board.has_legal_moves_remaining(current_player_symbol):
|
||||
return self.utility(board, current_player_symbol), (None, None)
|
||||
|
||||
best_found_value = math.inf, (None, None)
|
||||
|
||||
for successor in self.successors(board, current_player_symbol):
|
||||
new_board = deepcopy(board)
|
||||
new_board.play_move(*successor, current_player_symbol)
|
||||
|
||||
next_player_symbol = "O" if current_player_symbol == "X" else "X"
|
||||
|
||||
new_board_minimax = self.minimax_max_value(new_board, next_player_symbol)
|
||||
|
||||
if new_board_minimax[0] < best_found_value[0]:
|
||||
best_found_value = new_board_minimax[0], successor
|
||||
|
||||
return best_found_value
|
||||
|
||||
def minimax_decision(self, board, current_player_symbol):
|
||||
minimax_result = self.minimax_max_value(board, current_player_symbol)
|
||||
|
||||
return minimax_result[1]
|
||||
|
||||
def get_move(self, board):
|
||||
return self.minimax_decision(board, self.board_symbol)
|
||||
|
||||
|
||||
class OthelloBoard:
|
||||
EMPTY = "."
|
||||
DIRECTIONS = {
|
||||
"N": {"column": 0, "row": 1},
|
||||
"NE": {"column": 1, "row": 1},
|
||||
"E": {"column": 1, "row": 0},
|
||||
"SE": {"column": 1, "row": -1},
|
||||
"S": {"column": 0, "row": -1},
|
||||
"SW": {"column": -1, "row": -1},
|
||||
"W": {"column": -1, "row": 0},
|
||||
"NW": {"column": -1, "row": 1},
|
||||
}
|
||||
|
||||
def __init__(self, num_columns, num_rows, player_1_board_symbol, player_2_board_symbol):
|
||||
self._columns = num_columns
|
||||
self._rows = num_rows
|
||||
|
||||
self.grid = [[self.EMPTY for _ in range(self.num_rows)] for _ in range(self.num_columns)]
|
||||
|
||||
self.set_cell(self.num_columns // 2, self.num_rows // 2, player_1_board_symbol)
|
||||
self.set_cell(self.num_columns // 2 - 1, self.num_rows // 2 - 1, player_1_board_symbol)
|
||||
self.set_cell(self.num_columns // 2 - 1, self.num_rows // 2, player_2_board_symbol)
|
||||
self.set_cell(self.num_columns // 2, self.num_rows // 2 - 1, player_2_board_symbol)
|
||||
|
||||
self.display()
|
||||
|
||||
@property
|
||||
def num_columns(self):
|
||||
return self._columns
|
||||
|
||||
@property
|
||||
def num_rows(self):
|
||||
return self._rows
|
||||
|
||||
def in_bounds(self, column, row):
|
||||
return (0 <= column < self.num_columns) and (0 <= row < self.num_rows)
|
||||
|
||||
def get_cell(self, column, row):
|
||||
assert self.in_bounds(column, row)
|
||||
return self.grid[row][column]
|
||||
|
||||
def set_cell(self, column, row, value):
|
||||
assert self.in_bounds(column, row)
|
||||
self.grid[row][column] = value
|
||||
|
||||
def cell_empty(self, column, row):
|
||||
assert self.in_bounds(column, row)
|
||||
return self.grid[row][column] == self.EMPTY
|
||||
|
||||
def display(self):
|
||||
for row_index in reversed(range(self.num_rows)):
|
||||
print("{}:| ".format(row_index), end="")
|
||||
for column_index in range(self.num_rows):
|
||||
print("{} ".format(self.get_cell(column_index, row_index)), end="")
|
||||
print()
|
||||
|
||||
print(" -", end="")
|
||||
for _ in range(self.num_columns):
|
||||
print("--", end="")
|
||||
print()
|
||||
|
||||
print(" ", end="")
|
||||
for column_index in range(self.num_columns):
|
||||
print("{} ".format(column_index), end="")
|
||||
print("\n")
|
||||
|
||||
def set_coordinates_in_direction(self, column, row, direction):
|
||||
direction_offsets = self.DIRECTIONS[direction]
|
||||
|
||||
return column + direction_offsets["column"], row + direction_offsets["row"]
|
||||
|
||||
def check_endpoint(self, column, row, player_symbol, direction, match_symbol):
|
||||
if not self.in_bounds(column, row) or self.cell_empty(column, row):
|
||||
return False
|
||||
|
||||
if match_symbol:
|
||||
if self.get_cell(column, row) == player_symbol:
|
||||
return True
|
||||
else:
|
||||
next_column, next_row = self.set_coordinates_in_direction(column, row, direction)
|
||||
return self.check_endpoint(next_column, next_row, player_symbol, direction, match_symbol)
|
||||
|
||||
else:
|
||||
if self.get_cell(column, row) == player_symbol:
|
||||
return False
|
||||
else:
|
||||
next_column, next_row = self.set_coordinates_in_direction(column, row, direction)
|
||||
return self.check_endpoint(next_column, next_row, player_symbol, direction, not match_symbol)
|
||||
|
||||
def flip_pieces_helper(self, column, row, player_symbol, direction):
|
||||
if self.get_cell(column, row) == player_symbol:
|
||||
return 0
|
||||
|
||||
self.set_cell(column, row, player_symbol)
|
||||
next_column, next_row = self.set_coordinates_in_direction(column, row, direction)
|
||||
|
||||
return 1 + self.flip_pieces_helper(next_column, next_row, player_symbol, direction)
|
||||
|
||||
def flip_pieces(self, column, row, player_symbol):
|
||||
assert self.in_bounds(column, row)
|
||||
|
||||
pieces_flipped = 0
|
||||
|
||||
for direction in self.DIRECTIONS.keys():
|
||||
next_column, next_row = self.set_coordinates_in_direction(column, row, direction)
|
||||
if self.check_endpoint(next_column, next_row, player_symbol, direction, False):
|
||||
pieces_flipped += self.flip_pieces_helper(next_column, next_row, player_symbol, direction)
|
||||
|
||||
return pieces_flipped
|
||||
|
||||
def has_legal_moves_remaining(self, player_symbol):
|
||||
for row in range(self.num_rows):
|
||||
for column in range(self.num_columns):
|
||||
if self.cell_empty(column, row) and self.is_legal_move(column, row, player_symbol):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_legal_move(self, column, row, player_symbol):
|
||||
if not self.in_bounds(column, row) or not self.cell_empty(column, row):
|
||||
return False
|
||||
|
||||
for direction in self.DIRECTIONS.keys():
|
||||
next_column, next_row = self.set_coordinates_in_direction(column, row, direction)
|
||||
if self.check_endpoint(next_column, next_row, player_symbol, direction, False):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def play_move(self, column, row, player_symbol):
|
||||
self.set_cell(column, row, player_symbol)
|
||||
self.flip_pieces(column, row, player_symbol)
|
||||
|
||||
def count_score(self, player_symbol):
|
||||
score = 0
|
||||
|
||||
for row in self.grid:
|
||||
for current_column_value in row:
|
||||
if current_column_value == player_symbol:
|
||||
score += 1
|
||||
|
||||
return score
|
||||
|
||||
|
||||
class GameDriver:
|
||||
VALID_PLAYER_TYPES = ["human", "minimax"]
|
||||
|
||||
def __init__(self, player_1_type, player_2_type, num_columns, num_rows):
|
||||
self.player_1 = (HumanPlayer(1, "X") if player_1_type == "human" else MinimaxPlayer(1, "X"))
|
||||
self.player_2 = (HumanPlayer(2, "O") if player_2_type == "human" else MinimaxPlayer(2, "O"))
|
||||
|
||||
self.board = OthelloBoard(num_columns, num_rows, self.player_1.board_symbol, self.player_2.board_symbol)
|
||||
|
||||
def display(self):
|
||||
print()
|
||||
print("Player 1 ({}) score: {}".format(self.player_1.board_symbol,
|
||||
self.board.count_score(self.player_1.board_symbol)))
|
||||
print("Player 2 ({}) score: {}".format(self.player_2.board_symbol,
|
||||
self.board.count_score(self.player_2.board_symbol)))
|
||||
self.board.display()
|
||||
|
||||
def process_move(self, current_player):
|
||||
invalid_move = True
|
||||
|
||||
while invalid_move:
|
||||
column, row = current_player.get_move(self.board)
|
||||
|
||||
if not self.board.is_legal_move(column, row, current_player.board_symbol):
|
||||
print("Invalid move")
|
||||
else:
|
||||
print("Selected move: col = {}, row = {}".format(column, row))
|
||||
self.board.play_move(column, row, current_player.board_symbol)
|
||||
invalid_move = False
|
||||
|
||||
def run(self):
|
||||
game_running = True
|
||||
|
||||
current_player = self.player_1
|
||||
cant_move_counter = 0
|
||||
|
||||
while game_running:
|
||||
print("Player {} ({}) move:".format(current_player.player_number, current_player.board_symbol))
|
||||
|
||||
if self.board.has_legal_moves_remaining(current_player.board_symbol):
|
||||
cant_move_counter = 0
|
||||
self.process_move(current_player)
|
||||
self.display()
|
||||
else:
|
||||
print("No moves available for player {}".format(current_player.player_number))
|
||||
cant_move_counter += 1
|
||||
|
||||
if cant_move_counter == 2:
|
||||
game_running = False
|
||||
|
||||
current_player = (self.player_1 if current_player == self.player_2 else self.player_2)
|
||||
|
||||
player_1_score = self.board.count_score(self.player_1.board_symbol)
|
||||
player_2_score = self.board.count_score(self.player_2.board_symbol)
|
||||
|
||||
print()
|
||||
|
||||
if player_1_score == player_2_score:
|
||||
print("Tie Game")
|
||||
elif player_1_score > player_2_score:
|
||||
print("Player 1 Wins")
|
||||
else:
|
||||
print("Player 2 Wins")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="A command line version of the Othello board game")
|
||||
|
||||
parser.add_argument("player_1_type", choices=["human", "minimax"], help="Type for player 1")
|
||||
parser.add_argument("player_2_type", choices=["human", "minimax"], help="Type for player 2")
|
||||
args = parser.parse_args()
|
||||
|
||||
game = GameDriver(args.player_1_type, args.player_2_type, 4, 4)
|
||||
game.run()
|
||||
Reference in New Issue
Block a user