Added missing classes from final year at OSU

This commit is contained in:
2019-06-17 14:04:15 -07:00
parent 8fa1ffb1b0
commit c717a0316f
166 changed files with 653934 additions and 308 deletions

View File

@@ -0,0 +1,5 @@
To run, use...
python3 othello.py human minimax
You must use python3!

View File

@@ -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";
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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_ */

View File

@@ -0,0 +1,9 @@
#include "Player.h"
Player::Player(char symb) : symbol(symb) {
}
Player::~Player() {
}

View File

@@ -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

View File

@@ -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()