HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Reversi (Othello) in Python

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
othelloreversipython

Problem

I'm a beginner-intermediate programmer who's mostly used Java. I taught myself Python and made this engine for playing Reversi. Tips on Pythonic ways to accomplish tasks and/or general advice appreciated!

`from ast import literal_eval as eval

board_size = 8
BLACK = '\u26AB'
WHITE = '\u26AA'
EMPTY = '\u2B1c'
offsets = ((0,1),(1,1),(1,0),(1,-1),(0,-1),(-1,-1),(-1,0),(-1,1))

def inverse(piece):
return BLACK if piece is WHITE else WHITE

def main():
if input('Display instructions? [Y/N]: ').lower() == 'y':
instructions()
board = create_board()
piece = BLACK
while has_valid_move(board, piece):
game_loop(board, piece)
if has_valid_move(board, inverse(piece)):
piece = inverse(piece)
print_board(board)
black, white = 0,0
for row in board:
for token in row:
if token is WHITE: white += 1
if token is BLACK: black += 1
if black == white:
print("It's a tie!")
else:
print()
print('{token} is the winner!' % (BLACK if black>white else WHITE))
return

def instructions():
print('''
Reversi, also known as Othello, is a strategy board game for two players.
The two players alternate placing Black and White tokens on the board,
respectively, and the winner is whomever has the most tokens of their color
on the board when no legal moves remain.'''\
)
input('\nPress Enter to continue...')
print('''
A legal move is one that causes other pieces to flip between White and Black.
A move causes pieces of the opposite color to flip when placed down if and
only if there is an unbroken line between it and another piece of the same
color consisting of pieces of the opposite color.'''\
)
input('\nPress Enter to continue...')
print('''
To play this version, when prompted, enter the cartesian coordinates of the
location that you would like to place the piece. The upper left corner of the
board is 0,0 and the lower right is {0},

Solution

Here are my quick impressions. Please note that I haven't gone through your code line by line, and I hope someone would offer a detailed review for you soon.

Let's look at function:

def is_valid_move(board, piece, move):
    if board[move[0]][move[1]] is not EMPTY: return False
    for offset in offsets:
        check = [move[0]+offset[0], move[1]+offset[1]]
        while 0<=check[0]<board_size-1 and 0<=check[1]<board_size-1 and \
              board[check[0]][check[1]] is inverse(piece):
            check[0] += offset[0]
            check[1] += offset[1]
            if board[check[0]][check[1]] is piece:
                return True
    return False


On the fly, there are some issues which could be seen.

To understand the code, I would need to understand the structure of board, move and piece. And to do that, I would need to go back in stack, and see their composition.

Here are some ideas which could alleviate this:

  • Use of descriptive data structures rather than simple 2-D arrays. Instead of saying move[0], move[1], move.x and move.y is much more clearer. Namedtuple is one such structure.



  • Document, document, and document. You mentioned that you would rather have code self-explanatory. That is good and excellent aim. However, as program size would grow (and I hope you would start writing programs which are non-trivial in size) and more importantly, as number of people associated with project grows, importance of documentation grows. When someone else wants to use your function (and six months down the line, you also become "someone else"), you don't want them to peruse other functions to find out what exactly your function does, in what format does it accept argument, and what is it return format. Check out what docstrings are, and use them. Extensively.



  • This brings me to third point. Use classes. Since you don't want to explain to everyone again and again what is board, and how to represent move,plus it makes logical sense, encapsulate related functions and data structures in one entity. Using classes IMO would have been lot cleaner.



I would love to come back to this question later, and offer a detailed review on other metrics.

Here is some general Python advise:

  • PEP 8. If you have just started Python, this is must read.



  • Avoid global constants. (Here, if you would have used classes, this would be remedied)



  • I noticed you were using return in several functions when it was not strictly necessary.



Caught my eye:
Instead of if is_valid_move(board, piece, (y,x)): return True can use return is_valid_move(board, piece, (y,x))

Code Snippets

def is_valid_move(board, piece, move):
    if board[move[0]][move[1]] is not EMPTY: return False
    for offset in offsets:
        check = [move[0]+offset[0], move[1]+offset[1]]
        while 0<=check[0]<board_size-1 and 0<=check[1]<board_size-1 and \
              board[check[0]][check[1]] is inverse(piece):
            check[0] += offset[0]
            check[1] += offset[1]
            if board[check[0]][check[1]] is piece:
                return True
    return False

Context

StackExchange Code Review Q#125334, answer score: 2

Revisions (0)

No revisions yet.