I'm studying Object-oriented Programming and decided to apply some things doing an Object-oriented Tic-Tac-Toe.
I wanted to know if you have some hints of what to improve and why!
class Board:
"""Represents one board to a Tic-Tac-Toe game."""
def __init__(self):
"""Initializes a new board.
A board is a dictionary which the key is the position in the board
and the value can be 'X', 'O' or ' ' (representing an empty position
in the board.)"""
self.board = {
"TL": " ", "TM": " ", "TR": " ",
"ML": " ", "MM": " ", "MR": " ",
"BL": " ", "BM": " ", "BR": " "}
def print_board(self):
"""Prints the board."""
print(self.board["TL"] + "|" + self.board["TM"] \
+ "|" + self.board["TR"] + "|")
print("-+" * 3)
print(self.board["ML"] + "|" + self.board["MM"] \
+ "|" + self.board["MR"] + "|")
print("-+" * 3)
print(self.board["BL"] + "|" + self.board["BM"] \
+ "|" + self.board["BR"] + "|")
def _is_valid_move(self, position):
if self.board[position] is " ":
return True
return False
def change_board(self, position, type):
"""Receive a position and if the player is 'X' or 'O'.
Checks if the position is valid, modifies the board and returns the modified board.
Returns None if the move is not valid."""
if self._is_valid_move(position):
self.board[position] = type
return self.board
return None
def is_winner(self, player):
"""Returns True if the player won and False otherwise."""
if self.board["TL"] == player.type and self.board["TM"] == player.type and self.board["TR"] == player.type or \
self.board["ML"] == player.type and self.board["MM"] == player.type and self.board["MR"] == player.type or \
self.board["BL"] == player.type and self.board["BM"] == player.type and self.board["BR"] == player.type or \
self.board["TL"] == player.type and self.board["ML"] == player.type and self.board["BL"] == player.type or \
self.board["TM"] == player.type and self.board["MM"] == player.type and self.board["BM"] == player.type or \
self.board["TR"] == player.type and self.board["MR"] == player.type and self.board["BR"] == player.type or \
self.board["TL"] == player.type and self.board["MM"] == player.type and self.board["BR"] == player.type or \
self.board["BL"] == player.type and self.board["MM"] == player.type and self.board["TR"] == player.type:
return True
return False
class Player:
"""Represents one player."""
def __init__(self, type):
"""Initializes a player with type 'X' or 'O'."""
self.type = type
def __str__(self):
return "Player {}".format(self.type)
class Game:
"""Represents a Tic-Tac-Toe game.
The game defines player 1 always playing with 'X'."""
def __init__(self):
"""Initilize 2 Players and one Board."""
self.player1 = Player("X")
self.player2 = Player("O")
self.board = Board()
def print_valid_entries(self):
"""Prints the valid inputs to play the game."""
print("""
TL - top left | TM - top middle | TR - top right
ML - middle left | MM - center | MR - middle right
BL - bottom left | BM - bottom middle | BR - bottom right""")
def printing_board(self):
"""Prints the board."""
self.board.print_board()
def change_turn(self, player):
"""Changes the player turn.
Receives a player and returns the other."""
if player is self.player1:
return self.player2
else:
return self.player1
def won_game(self, player):
"""Returns True if the player won the game, False otherwise."""
return self.board.is_winner(player)
def modify_board(self, position, type):
"""Receives position and player type ('X' or 'O').
Returns modified board if position was valid.
Asks to player try a different position otherwise."""
if self.board.change_board(position, type) is not None:
return self.board.change_board(position, type)
else:
position = input("Not available position. Please, try again: ")
return self.board.change_board(position, type)
def play():
game = Game()
game.print_valid_entries()
player = game.player1
num = 9
while num > 0:
num -= 1
game.printing_board()
position = input("{} turn, what's your move? ".format(player))
game.modify_board(position, player.type)
if game.won_game(player):
print("{} is the Winner!".format(player))
break
else:
player = game.change_turn(player)
if num == 0:
print("Game over! It's a tie!")
if __name__ == "__main__":
play()
2 Answers 2
Code readability and style
I would recommend you have a look at PEP 8, which is Python's official style guide.
Let's introduce you to f-strings
-
To create an f-string, prefix the string with the letter "
f
". The string itself can be formatted in much the same way that you would withstr.format()
. f-strings provide a concise and convenient way to embed python expressions inside string literals for formatting.
So, I would write these three statements -
# rest of the code def __str__(self): return "Player {}".format(self.type) # rest of the code position = input("{} turn, what's your move? ".format(player)) # rest of the code print("{} is the Winner!".format(player))
Like this -
# rest of the code
def __str__(self):
return f"Player {self.type}"
# rest of the code
position = input(f"{player} turn, what's your move? ")
# rest of the code
print(f"{player} is the Winner!")
See how concise it can get?
From PEP 8 -
PEP 257 describes good docstring conventions. Note that most importantly, the
"""
that ends a multiline docstring should be on a line by itself -"""Return a foobang Optional plotz says to frobnicate the bizbaz first. """
For one liner docstrings, please keep the closing
"""
on the same line.
So, for example, this -
"""Receives position and player type ('X' or 'O'). Returns modified board if position was valid. Asks to player try a different position otherwise."""
Should actually be written as -
"""Receives position and player type ('X' or 'O').
Returns modified board if position was valid.
Asks to player try a different position otherwise.
"""
Also, since you have descriptively named functions, you don't need those unnecessary comments explaining what your function does. For example, this does not need a comment -
def printing_board(self): """Prints the board.""" self.board.print_board()
We know you're printing the board; it says in the function itself - def printing_board(self)
.
Also, good use of the if '__name__' == __'main__':
guard. Most people don't even attempt to use it.
Note that the trailing \
solutions are not recommended by PEP 8. One reason is that if space is added by mistake after a \
it might not show in your editor, and the code becomes syntactically incorrect.
The PEP changed at https://hg.python.org/peps/rev/7a48207aaab6 to explicitly discourage backslashes.
The preferred way of wrapping long lines is by using Python's implied line continuation inside parentheses, brackets, and braces. Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation.
Another thing is that, here (for example) -
if self.board["TL"] == player.type and self.board["TM"] == player.type and self.board["TR"] == player.type or \ self.board["ML"] == player.type and self.board["MM"] == player.type and self.board["MR"] == player.type or \ self.board["BL"] == player.type and self.board["BM"] == player.type and self.board["BR"] == player.type or \ self.board["TL"] == player.type and self.board["ML"] == player.type and self.board["BL"] == player.type or \ self.board["TM"] == player.type and self.board["MM"] == player.type and self.board["BM"] == player.type or \ self.board["TR"] == player.type and self.board["MR"] == player.type and self.board["BR"] == player.type or \ self.board["TL"] == player.type and self.board["MM"] == player.type and self.board["BR"] == player.type or \ self.board["BL"] == player.type and self.board["MM"] == player.type and self.board["TR"] == player.type:
the lines are too long. According to PEP 8 -
Limit all lines to a maximum of 79 characters.
Therefore, these statements could alternatively be written as -
def is_winner(self, player):
player_type = player.type
runs = [
# horizontal
["TL", "TM", "TR"],
["ML", "MM", "MR"],
["BL", "BM", "BR"],
# vertical
["TL", "ML", "BL"],
["TM", "MM", "BM"],
["TR", "MR", "BR"],
# diagonal
["TL", "MM", "BR"],
["BL", "MM", "TR"]
]
for a, b, c in runs:
if self.board[a] == self.board[b] == self.board[c] == player_type:
return True
return False
Overall, in terms of code readability and style, this is what you need to improve. You should make your code more PEP 8 compliant.
Hope this helps!
-
1\$\begingroup\$ Thank you!! That was really helpful, I'll look PEP 8 now, with more attention. \$\endgroup\$Andressa Cabistani– Andressa Cabistani2019年06月17日 05:50:58 +00:00Commented Jun 17, 2019 at 5:50
-
1\$\begingroup\$ So, I was changing my code using those hints but the last one didn't work in here. Now, it doesn't access the value player.type correctly, so 'X O X' is a win point. I tried
self.board["TL"] and self.board["TM"] and self.board["TR"] == player.type
too, but it didn't work as well. Any idea of what happened?? \$\endgroup\$Andressa Cabistani– Andressa Cabistani2019年06月17日 12:28:30 +00:00Commented Jun 17, 2019 at 12:28 -
1\$\begingroup\$ @AndressaCabistani - I have edited my answer to make some changes. This should definitely work. Sorry for any confusion :( \$\endgroup\$Justin– Justin2019年06月17日 15:49:01 +00:00Commented Jun 17, 2019 at 15:49
-
1\$\begingroup\$ Now it works perfectly!! \$\endgroup\$Andressa Cabistani– Andressa Cabistani2019年06月17日 16:09:24 +00:00Commented Jun 17, 2019 at 16:09
-
1\$\begingroup\$ @AndressaCabistani - I'm glad it helped you :) \$\endgroup\$Justin– Justin2019年06月17日 16:09:49 +00:00Commented Jun 17, 2019 at 16:09
You can use list comprehension as well as the built-in all()
and any()
methods to make your is_winner()
method more concise and readable:
def is_winner(self, player):
"""Returns True if the player won and False otherwise."""
row1 = [self.board[val] for val in ["TL","TM","TR"]]
row2 = [self.board[val] for val in ["ML","MM","MR"]]
row3 = [self.board[val] for val in ["BL","BM","BR"]]
col1 = [self.board[val] for val in ["TL","ML","BL"]]
col2 = [self.board[val] for val in ["TM","MM","BM"]]
col3 = [self.board[val] for val in ["TR","MR","BR"]]
dia1 = [self.board[val] for val in ["TL","MM","BR"]]
dia2 = [self.board[val] for val in ["TR","MM","BL"]]
wins = [row1, row2, row3, col1, col2, col3, dia1, dia2]
isPlayer = lambda cell: cell == player.type
isWinner = lambda seq: all(map(isPlayer, seq))
if any(map(isWinner, wins)):
''' Returns true if any possible win is a win '''
return True
else:
return False
The code to get all of the possible win combinations could have been made more concise if the board was represented by a 2D array rather than a dict
as you have it; but the explicit nature of assigning the values of each row still has its benefits.
Messed around and made a more DRY way of building the wins
array, but the nested list comprehension is arguably less readable/understandable. Nonetheless, here it is just for fun:
wins = [[self.board[cell] for cell in seq]
for seq in [
["TL","TM","TR"], # Row 1
["ML","MM","MR"], # Row 2
["BL","BM","BR"], # Row 3
["TL","ML","BL"], # Col 1
["TM","MM","BM"], # Col 2
["TR","MR","BR"], # Col 3
["TL","MM","BR"], # Dia 1
["TR","MM","BL"] # Dia 2
]
]
Explore related questions
See similar questions with these tags.