Text based othello game

Text based othello game

Background

This project is the first of a two-part sequence that offers you to opportunity to build your own game. The first of the two projects focuses on developing a clean set of game logic and a console-mode “outer shell” that you’ll use to test it. The second one pivots into building a graphical user interface atop the same game logic, focusing on drawing graphics and handling a variety of user input. Along the way, we’ll focus on finding a design that serves both purposes, on finding ways to simplify our code by eliminating duplication of boilerplate, and continuing our journey into understanding the mechanics and the benefits of classes and object-oriented programming in Python.

The game of Othello

This project and the subsequent one will ask you to implement a game called Othello. Othello (also known as Reversi) is a well-known two-player strategy game. The game is played on a rectangular board divided into a grid — usually 8×8, though the size of the grid can vary. Players alternately place discs on the game board; one player’s discs are black and the other player’s are white. When discs are placed on the game board, other discs already on the board are flipped (i.e., a black disc becomes a white disc or vice versa). The game concludes when every square on the grid contains a disc, or when neither player is able to make a legal move; the winning player is generally the one who has more discs on the board at the end of the game, though there are alternate ways to determine a winner.

The program

This project asks you to implement the game logic of Othello, along with a console-mode program with a very Spartan user interface that you’ll use to test it — and that we’ll use to automatically test it, making it crucial that you get the format of the program’s input and output right according to the specification below. Spacing, capitalization, and other seemingly-minor details are critical.

Using your test program, you’ll be capable of playing a single game of Othello on a single computer. There are a handful of options that allow you to specify, before the game begins, how the game will be played. The program begins by reading input that selects these options; the game is then played by requiring input directly into the console that specifies a player’s next move, continuing until the game is complete, at which point the program ends.

When you’re finished, you’ll have the game logic that will form the basis of your completed version of Othello in the next project.

A detailed look at how your program should behave

  • Your program begins by printing a line of output specifying which of the two sets of Othello rules are supported by your program, by printing either FULL or SIMPLE.
  • Your program then reads four lines of input, specifying the options that will be used to determine how the game will be played.
    • The first line specifies the number of rows on the board, which will be an even integer between 4 and 16 (inclusive).
    • The second line specifies the number of columns on the board, which will be an even integer between 4 and 16 (inclusive) and does not have to be the same as the number of rows.
    • The third line specifies which of the players will move first: B for black, W for white.
    • The fourth line selects how the game is won: > means that the player with the most discs on the board at the end of the game wins, < means the player with the fewest.
  • Next, your program will read the initial contents of the board. To make it simpler to test a variety of scenarios, your game will need to support any initial arrangement of discs, so it will be necessary to read the entire contents of the board from the input. It will be appear in the input in this form:
    • Each row will occupy a single line of input, with one space separating the cells in that row. Each cell will be specified as either B (if a black disc is in that cell), W (if a white disc is in that cell), or. (if the cell is empty).
    • The number of rows and columns described in this way will match the number of rows and columns specified in the first two lines of the input.
    • For example, if there are 8 rows and 8 columns on the board, the initial contents of the board might appear in the input this way:
  • . . . . . . . .
  • . . . . . . . .
  • . . . . . . . .
  • . B W B W . . .
  • . . . W B . . .
  • . . . . B W . .
  • . . . . . B . .
  • . . . . . . . .
  • After the initial arrangement of the board is specified in the input, the game begins, proceeding one move a time until it’s complete.
    • Before each turn, the following information is printed to the console:
      • The character B, followed by a colon and a space, followed by the number of discs on the board that are black. This is followed by two spaces, the character W, a colon and a space, and the number of discs on the board that are white.
      • The contents of the board, with each row occupying one line of output, and one space separating each pair of cells. Each cell is written either as a B (if a black tile occupies that cell), a W (if a white tile occupies that cell), or a . (if no tile occupies that cell). For example:
  • . . . . . . . .
  • . . . . . . . .
  • . . . . . . . .
  • . . . B W . . .
  • . . . W B . . .
  • . . . . . . . .
  • . . . . . . . .
  • . . . . . . . .
      • The word TURN (in all-caps), followed by a colon and a space, followed by B if it’s the black player’s turn, W if it’s white’s turn.
    • After printing the information above, the program reads a line of input specifying the current player’s move. The move is specified as two integers on a line, separated by a space. The first integer specifies the row number (with 1 being the top row, 2 being the row below that, and so on) and the second integer specifies the column number (with 1 being the leftmost column, 2 being the one to the right of that, and so on).
      • If the move is valid, print the word VALID alone on a line, apply that move (i.e., make the appropriate changes to the state of the game) and then continue the game..
      • If the move is invalid, print the word INVALID alone on a line and wait for the user to specify another move.
  • At the conclusion of the game (i.e., after the last move is made and there are no more legal moves on the board), the final state of the game is printed to the console, quite similarly to how it’s printed before each turn:
    • The character B, followed by a colon and a space, followed by the number of discs on the board that are black. This is followed by two spaces, the character W, a colon and a space, and the number of discs on the board that are white.
    • The contents of the board, in the same format described above.
    • The word WINNER (in all-caps), followed by a colon and a space, followed by B if the black player has won, W if the white player has won, and NONE if no player has won (i.e., the final score is tied).

The program ends when the game is over.

A complete example of program execution

Below are two examples of the program’s execution:

  • On the left is an example of a complete game, for a version that implements the full Othello rules.
  • On the right is an example of a complete game, for a version that implements the simplified Othello rules (see below for more details about these).

Of course, your program will only be required to match one of these or the other, depending on which set of Othello rules you implemented.

Boldfaced, italicized text indicates input, while normal text indicates output.

FULL                     SIMPLE

44

44

BB

>>

. . . .. . . .

. B W .. B W .

. W B .. W B .

. . . .. . . .

B: 2  W: 2               B: 2  W: 2

. . . .                  . . . .

. B W .                  . B W .

. W B .                  . W B .

. . . .                  . . . .

TURN: B                  TURN: B

2 41 3

VALID                    VALID

B: 4  W: 1               B: 4  W: 1

. . . .                  . . B .

. B BB                  . B B .

. W B .                  . W B .

. . . .                  . . . .

TURN: W                  TURN: W

1 23 4

VALID                    VALID

B: 3  W: 3               B: 2  W: 4

. W . .                  . . B .

. W B B                  . B W .

. W B .                  . W WW

. . . .                  . . . .

TURN: B                  TURN: B

1 44 3

INVALID                  VALID

1 1                      B: 6  W: 1

VALID                    . . B .

B: 5  W: 2               . B W .

B W . .                  . B BB

. B BB                  . . B .

. W B .                  TURN: W

. . . .                  4 4

TURN: W                  VALID

1 4                      B: 3  W: 5

VALID                    . . B .

B: 4  W: 4               . B W .

B W . W                  . B W W

. B W B                  . . W W

. W B .                  TURN: B

. . . .                  2 4

TURN: B                  VALID

1 3                      B: 7  W: 2

VALID                    . . B .

B: 7  W: 2               . B BB

B BB W                  . B BB

. B BB                  . . W W

. W B .                  TURN: W

. . . .                  2 4

TURN: W                  INVALID

3 41 4

VALID                    VALID

B: 5  W: 5               B: 4  W: 6

B BB W                  . . W W

. B B W                  . B W W

. W WW                  . B BB

. . . .                  . . W W

TURN: B                  TURN: B

4 21 2

VALID                    VALID

B: 7  W: 4               B: 7  W: 4

B BB W                  . B B W

. B B W                  . B B W

. B W W                  . B BB

. B . .                  . . W W

TURN: W                  TURN: W

2 14 2

VALID                    VALID

B: 5  W: 7               B: 5  W: 7

B BB W                  . B B W

W WWW                  . B B W

. B W W                  . W W B

. B . .                  . W WW

TURN: B                  TURN: B

4 43 1

VALID                    VALID

B: 8  W: 5               B: 8  W: 5

B BB W                  . B B W

W B W W                  . B B W

. B B W                  B B W B

. B . B                  . B W W

TURN: W                  TURN: W

4 14 1

VALID                    VALID

B: 7  W: 7               B: 5  W: 9

B BB W                  . B B W

W B W W                  . B B W

. W B W                  WWW B

W B . B                  W WWW

TURN: B                  TURN: B

3 12 1

VALID                    VALID

B: 10  W: 5              B: 8  W: 7

B BB W                  . B B W

B B W W                  B BB W

B BB W                  B B W B

W B . B                  W WWW

TURN: W                  TURN: W

4 31 1

VALID                    VALID

B: 8  W: 8               B: 5  W: 11

B BB W                  WW B W

B B W WWW B W

B B W W                  B B W B

W WW B                  W WWW

WINNER: NONE             WINNER: W

A couple of “gotchas” to be aware of in the game logic

For the most part, Othello games proceed with players moving alternately, and continue until all cells on the grid contain a disc. However, there are a couple of wrinkles that you’ll need to be sure you handle:

  • Sometimes, a player will make a move and, as a result, the opposite player will have no valid moves available (i.e., there is no cell in the grid in which the opposite player can move afterward). In that case, the turn reverts back to the player who just moved, and that player moves again.
  • Occasionally, neither player will have a valid move on the board, even though there are still empty cells in the grid. In this case, the game immediately ends and the winner is determined based on the number of discs each player has on the board.

Simplified Othello rules

The simplified Othello rules are as follows:

  • The same configuration options need to be specified in the same order (i.e., number of rows and columns, who moves first, how the tiles are arranged at the start of the game, and whether more or fewer tiles wins the game) and they have the same meanings as in the full rules.
  • A player can make a valid move anywhere on the board that is adjacent to a tile (i.e., one cell away in any of the eight directions) occupied by a tile belonging to the other player.
  • When a player makes a move, any adjacent cell on the board (i.e., one cell away in any of the eight directions from where the move was made) containing a tile of the opponent’s color is flipped.
  • The game ends when neither player can make a legal move — when the board is completely filled with tiles, or when no open cells on the board are legal for either player.

The format of the input and output are also identical to what’s described in the section titled A detailed look at how your program should behave above; the only difference is the first line of the output (SIMPLE instead of FULL) and the logic for determining whether a move is legal and which tiles are flipped.

Module design

You are required to keep the code that implements the game logic entirely separate from the code that implements your console-mode user interface. To that end, you will be required to submit at least two modules: one that implements your game logic and another that implements your user interface. You’re welcome to break these two modules up further if you find it beneficial, but the requirement is that you keep these two parts of your program — the logic and the user interface — separate.

Module naming

Exactly one of your modules must be executable (i.e., should contain an if __name__ == ‘__main__’: block), namely the one that you would execute if you wanted to launch your console user interface and play your game. No other modules are permitted to have an if __name__ == ‘__main__’: block.

Using classes and exceptions to implement your game logic

Your game logic must consist of at least one class whose objects represent the current “state” of an Othello game, with methods that manipulate that state; you can feel free to implement additional classes, if you’d like. Classes offer us the ability to mix data together with the operations that safely manipulate that data; they allow us to create kinds of objects that don’t just know how to store things, but also to do things.

Even if your console user interface does error checking, your game logic must not assume the presence of a particular user interface, so it must check any parameters it’s given and raise an exception if the parameters are problematic (e.g., a non-existent row or column, an attempt to make an invalid move, an attempt to make a move after the game is over). Create your own exception class(es) to represent these error conditions.

Sanity-checking your output

We are providing a tool that you can use to sanity check whether you’ve followed some of the basic requirements above. It will only give you a “passing” result in these circumstances:

  • There is exactly one module that is executable (i.e., with an if __name__ == ‘__main__’: block in it).
  • Executing that module is enough to execute your program.
  • Your program reads its input and generates character-for-character correct input for one test scenario, which is similar to the example inputs and outputs shown above.

Because you may have chosen to implement either the full or the simple rules, the sanity-checker will read your first line of input and determine which rules it should be testing, then run an appropriate test automatically.

Solution 

gameBoard.py 

import random

class Game:

def __init__(self, nRows, nCols, grid, firstMove, winRule):

# validate arguments

ifnRows< 4 or nRows> 16 or nRows % 2 != 0:

raiseInvalidBoardSize(‘Invalid Board Height’)

ifnCols< 4 or nCols> 16 or nCols % 2 != 0:

raiseInvalidBoardSize(‘Invalid Board Width’)

if not self.colorValid(firstMove):

raiseInvalidDiscColor(“Invalid Disc Color”)

self.rows, self.cols = nRows, nCols

self.grid = grid

self.currPlayer = firstMove

self.winRule = winRule

defgetCurrentPlayer(self):

returnself.currPlayer

defgetOppositePlayer(self, currPlayer):

return ‘B’ if currPlayer == ‘W’ else ‘W’

defcolorValid(self, discColor):

returndiscColor in [‘B’, ‘W’]

defdisplayBoard(self):

for row in range(self.rows):

line = [self.grid

[row]

[col] for col in range(self.cols)]

print(” “.join(line))

defnumDiscs(self, discColor):

count = 0

for row in range(self.rows):

for col in range(self.cols):

ifself.grid

[row]

[col] == discColor:

count = count + 1

return count

defgetWinner(self):

numWhite = self.numDiscs(‘W’)

numBlack = self.numDiscs(‘B’)

ifnumBlack == numWhite:

return ‘NONE’

ifself.winRule == ‘<‘:

return ‘W’ if numWhite<numBlack else ‘B’

ifself.winRule == ‘>’:

return ‘W’ if numWhite>numBlack else ‘B’

defisOver(self):

ifself.hasMoves(self.getCurrentPlayer()):

return False

elifself.hasMoves(self.getOppositePlayer(self.currPlayer)):

return False

else:

return True

defgetNeigbors(self, row, col):

neigbors = []

for i in range(row – 1, row + 2):

for j in range(col – 1, col + 2):

if (i, j) != (row, col):

if i >= 0 and j >= 0 and i <self.rows and j <self.cols:

neigbors.append((i, j))

returnneigbors

defhasMoves(self, discColor):

for row in range(self.rows):

for col in range(self.cols):

ifself.moveValid(row, col, discColor):

return True

return False

defsetCell(self, row, col, discColor):

if row < 0 or row >= self.rows:

raiseInvalidBoardIndex(“Row index out of range”)

if col < 0 or col >= self.cols:

raiseInvalidBoardIndex(“Column index out of range”)

if not self.colorValid(discColor):

raiseInvalidDiscColor(“Invalid Disc Color”)

self.grid

[row]

[col] = discColor

defmoveValid(self, row, col, discColor):

if row < 0 or row >= self.rows:

raiseInvalidBoardIndex(“Row index out of range”)

if col < 0 or col >= self.cols:

raiseInvalidBoardIndex(“Column index out of range”)

if not self.colorValid(discColor):

raiseInvalidDiscColor(“Invalid Disc Color”)

ifself.grid

[row]

[col] == ‘.’:

for i, j in self.getNeigbors(row, col):

ifself.grid[i][j] != ‘.’ and self.grid[i][j] != discColor:

return True

return False

defplaceDisc(self, row, col):

self.setCell(row, col, self.currPlayer)

for i, j in self.getNeigbors(row, col):

ifself.grid[i][j] == self.getOppositePlayer(self.currPlayer):

self.setCell(i, j, self.currPlayer)

classInvalidBoardSize(Exception):

pass

classInvalidBoardIndex(Exception):

pass

classInvalidDiscColor(Exception):

pass 

playOthelllo.py 

fromgameBoard import Game

def main():

print(‘SIMPLE’)

numRows = int(input())

numCols = int(input())

firstMove = input()

winRule   = input()

grid = [input().split() for line in range(numRows)]

othello = Game(numRows, numCols, grid, firstMove, winRule)

while True:

numWhite = othello.numDiscs(‘W’)

numBlack = othello.numDiscs(‘B’)

print(‘B: {}  W: {}’.format(numBlack, numWhite))

othello.displayBoard()

ifothello.isOver():

print(‘WINNER: {}’.format(othello.getWinner()))

break

else:

print(‘TURN: {}’.format(othello.currPlayer))

while True:

line = input()

try:

values = line.split()

row, col = int(values[0]) – 1, int(values[1]) – 1

ifothello.moveValid(row, col, othello.currPlayer):

print(‘VALID’)

break

exceptValueError as e:

pass

print(‘INVALID’)

othello.placeDisc(row, col)

othello.currPlayer = othello.getOppositePlayer(othello.currPlayer)

if not othello.hasMoves(othello.currPlayer):

othello.currPlayer = othello.getOppositePlayer(othello.currPlayer)

if __name__ == ‘__main__’:

main()