Relatively new here so please let me know if I'm doing something wrong.
I'm mostly wondering if there's any additional way I can make this code more efficient. Specifically the moveset and win condition part of the code feels unnecessarily long or overcomplicated. Still a beginner in python, so any feedback regarding syntax is also appreciated! Thank you!
#Contains all basic variables for ease of coding
class Player():
symbols = ['O','X']
def __init__(self, number):
self.wins = 0
self.number = number
self.symbol = self.symbols[number]
#2D list to represent the 3x3 board
def createBoard():
a = ['', '', '']
b = ['', '', '']
c = ['', '', '']
return [a, b, c]
board = createBoard()
#Makes the board look nicer for user; numbers as placeholder for empty spaces
def printBoard():
i = 0
for row in board:
print("---------------")
for place in row:
i += 1
if place == '':
print("|",i,"|", end='')
else:
print("|",place,"|", end='')
print("")
print("---------------")
#Receives numbers to return x and y position of the 2d list
def getMove():
#Any other more efficient way for this?
moveset = {
'1': (0,0),
'2': (1,0),
'3': (2,0),
'4': (0,1),
'5': (1,1),
'6': (2,1),
'7': (0,2),
'8': (1,2),
'9': (2,2)
}
print("Please enter the number of the spot to place your move",end='')
xpos, ypos = moveset[input("> ")]
return xpos, ypos
#changes the board with received x and y
def placeMove(xpos, ypos, player):
global board
board[ypos][xpos] = player.symbol
#Checkes if the spot has been filled
def validateMove(xpos, ypos):
if board[ypos][xpos] != '':
print("Cannot place there!")
return False
return True
#Checks win conditions; any more efficient way for this as well?
def checkWin():
for row in board:
if row[0]==row[1]==row[2]:
return row[0]
for i in range(0,3):
if board[0][i]==board[1][i]==board[2][i]:
return board[0][i]
if board[0][0]==board[1][1]==board[2][2]:
return board[1][1]
elif board[0][2]==board[1][1]==board[2][0]:
return board[1][1]
for row in board:
for spot in row:
if spot == '':
return False
return 2
def switchPlayer(currentPlayer):
if currentPlayer.number == 0:
return 1
else:
return 0
#game loop
def game():
global board
playerList = [Player(0), Player(1)]
game = True
currentPlayer = playerList[0]
printBoard()
while game:
print("Current Player:",currentPlayer.number+1,)
xpos, ypos = getMove()
#repeats until valid input
while not validateMove(xpos, ypos):
xpos, ypos = getMove()
placeMove(xpos, ypos, currentPlayer)
printBoard()
if checkWin() == currentPlayer.symbol:
print("Player",currentPlayer.number+1, "wins!")
game = False
elif checkWin() == 2:
print("The game is a draw!")
game = False
else:
currentPlayer = playerList[switchPlayer(currentPlayer)]
#Should I just make the game() the main() function?
def main():
game()
if __name__ == "__main__":
main()
-
\$\begingroup\$ Not an answer, but you might want to read rosettacode.org/wiki/Tic-tac-toe#Python to see how others did it. \$\endgroup\$glenn jackman– glenn jackman2018年12月12日 22:50:35 +00:00Commented Dec 12, 2018 at 22:50
2 Answers 2
In addition to Reinderien's comments:
checkWin()
first checks board[0][0]==board[0][1]==board[0][2]
and returns the "winning" symbol (a string), if the match was found.
This means that, with the following board:
| |
---+---+---
X | X |
---+---+---
O | O | O
checkWin()
returns ''
, and checkWin() == currentPlayer.symbol
is false, so "O" doesn't win. And "X" can fill their row on their move, and will be declared the winner in two more moves!
The only player who can win, is the one that just made a move, so pass that player's symbol to the checkWin()
call, and explicitly check for that player winning:
def checkWin(symbol):
for row in board:
if row[0] == row[1] == row[2] == symbol:
return symbol
checkWin()
returns 3 different types of values!
str
if a winner is detectedbool
if there are still moves which can be madeint
if the game is tied.
This is just plain wrong. Pick one return type. For instance, you could use int
, and return 0
for game not over, 1
for a win by the current player, and -1
for a draw game.
Or you could use str
, and return "X"
or "O"
for a win by that player, "Cat's game"
for a draw, and ""
for the game not over. Then, created named constants for the values, and use those names in your code, instead of the actual values. Eg)
GAME_HAS_WINNER = 1
GAME_IS_A_DRAW = -1
GAME_IS_NOT_OVER = 0
# ...
def game():
# ...
while True:
# ...
state = checkWin(currentPlayer.symbol)
if state == GAME_HAS_WINNER:
# ... declare winner
break
elif state == GAME_IS_A_DRAW:
# ... declare a draw
break
else:
# ...
Better would be to create an Enum
for the return code, if you've learnt that.
You can simplify your createBoard()
& printBoard()
functions, and at the same time fix the bug in checkWin()
by initializing the board with the number characters, instead of empty strings.
def createBoard():
return [['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']]
def printBoard():
for row in board:
print("---------------")
for place in row:
print("|", place, "|", end='')
print("")
print("---------------")
You'll need a different way to check for a draw game. The simplest would be to remove that test from checkWin()
and change your while
loop into a for
loop that runs for at most 9 turns. If you finish the loop without break
-ing out of the loop, the else:
clause gets executed.
def game():
# ...
for _ in range(9):
# ...
if checkWin(currentPlayer.symbol) == GAME_HAS_WINNER:
# ... announce winner
break
# ...
else:
# ... announce draw game
You can use list comprehension to simplify checkWin()
. For instance, checking only for a win by a given symbol, and returning True
for a win, and False
otherwise:
def checkWin(symbol):
if any( all(board[i][j] == symbol for i in range(3)) for j in range(3)):
return True
if any( all(board[i][j] == symbol for j in range(3)) for i in range(3)):
return True
if all(board[i][i] == symbol for i in range(3)):
return True
return all(board[i][2-i] == symbol for i in range(3)):
symbols = ['O','X']
This will not change, so it doesn't need to be mutable. It's better represented as a string:
symbols = 'OX'
This:
def createBoard():
a = ['', '', '']
b = ['', '', '']
c = ['', '', '']
return [a, b, c]
can simply be
def create_board():
return [['']*3 for _ in range(3)]
Note that the convention for Python is snake_case, not camelCase.
getMove
can be made more efficient. You do not need a dictionary.
i = int(input('Please enter the number of the '
'spot to place your move > ')) - 1
return i%3, i//3
You're using board
as a global, which is a code smell. Most of these methods, along with the board member, should be put into a class.
This:
if currentPlayer.number == 0:
return 1
else:
return 0
can be:
return 1 - current_player.number
Your boolean game
variable is unnecessary. Rather than setting it to false, simply break out of the loop.
At the top of the file, there should be a shebang:
#!/usr/bin/env python3
Explore related questions
See similar questions with these tags.