I am just beginning to learn Python, and thought I would give myself a challenge using the lessons I have learned so far. I am following the Udemy course, Automate the Boring Stuff with Python. The point that I've reached so far has covered the basics of functions, handling errors, lists and dictionaries.
I wanted to see if I had a grasp of what I'd learned by making a tic-tac-toe game with certain features/challenges/objectives in mind:
- make use of loops, functions, methods, lists/dictionaries.
- the player would need to see the board and what spots were taken/left
- I wanted players to be able to type in the choice in upper/lower/mixed case
- the game had to recognize all win conditions, lose conditions, and stalemate conditions.
- the opponent (in this case, the CPU) would need to only have access to the choices remaining. For now, it would be random, but maybe I would come back to the code to have some form of AI.
- When the game ends, give the player the choice of starting a new game, and keep track of the score.
This is my code, and as far as I can see, it works and I managed to complete all the goals I set out to do:
import copy
import time
import random
import sys
##Board design to make it look pretty
def printBoard(board):
print('')
print(board['top-l'] + '|' + board['top-m'] + '|' + board['top-r'])
print('-----')
print(board['mid-l'] + '|' + board['mid-m'] + '|' + board['mid-r'])
print('-----')
print(board['low-l'] + '|' + board['low-m'] + '|' + board['low-r'])
print('')
##Function for initiating the board back to blank
def blankBoard(zeroBoard):
for value in zeroBoard:
zeroBoard[value] = ' '
##Function for initiating a New Game after a game over situation
def newGame(YorN):
print('\n ~ Would you like to play again? if yes, type ''Y'': ~ ')
newGame = input()
if newGame.upper() == 'Y':
print(' ~ okay, please wait while we set up a new game! ~ ')
blankBoard(theBoard)
time.sleep(random.randint(1,2))
printBoard(theBoard)
else:
print('\n ~ Bye and thank you for playing!! ~ ')
sys.exit()
##Function containing all the win conditions
def winCondition(winner):
if ((winner['top-l'] == winner['top-m'] == winner['top-r'] == 'X') or
(winner['mid-l'] == winner['mid-m'] == winner['mid-r'] == 'X') or
(winner['low-l'] == winner['low-m'] == winner['low-r'] == 'X') or
(winner['top-l'] == winner['mid-l'] == winner['low-l'] == 'X') or
(winner['top-m'] == winner['mid-m'] == winner['low-m'] == 'X') or
(winner['top-r'] == winner['mid-r'] == winner['low-r'] == 'X') or
(winner['top-l'] == winner['mid-m'] == winner['low-r'] == 'X') or
(winner['top-r'] == winner['mid-m'] == winner['low-l'] == 'X')):
print('\n ~ Congratulations! You are a winner! ')
return 'win'
##Function containing all the loss conditions
def loseCondition(loser):
if ((loser['top-l'] == loser['top-m'] == loser['top-r'] == 'O') or
(loser['mid-l'] == loser['mid-m'] == loser['mid-r'] == 'O') or
(loser['low-l'] == loser['low-m'] == loser['low-r'] == 'O') or
(loser['top-l'] == loser['mid-l'] == loser['low-l'] == 'O') or
(loser['top-m'] == loser['mid-m'] == loser['low-m'] == 'O') or
(loser['top-r'] == loser['mid-r'] == loser['low-r'] == 'O') or
(loser['top-l'] == loser['mid-m'] == loser['low-r'] == 'O') or
(loser['top-r'] == loser['mid-m'] == loser['low-l'] == 'O')):
print('\n ~ Aww man, you''re are a loser! ~ ')
return 'lose'
##Function for Computer Players Choice
def computerChoice(cpuChoice):
theBoardCPU = copy.deepcopy(theBoardChoices)
print(','.join(theBoardCPU.keys()))
cpuChoice = random.choice(list(theBoardCPU.keys()))
print(' ~ ' + cpuChoice + ' ~ ')
del theBoardChoices[cpuChoice]
theBoard[cpuChoice] = 'O'
printBoard(theBoard)
##Blank board to begin with and making a copy of the board for choices remaining, and finally printing a pretty board
theBoard = {'top-l': ' ', 'top-m': ' ', 'top-r': ' ',
'mid-l': ' ', 'mid-m': ' ', 'mid-r': ' ',
'low-l': ' ', 'low-m': ' ', 'low-r': ' '}
theBoardChoices = copy.deepcopy(theBoard)
printBoard(theBoard)
##The actual game
print(' ~ Hello player. What is your name? ~ ')
player1Name = input()
print(' ~ Hello ' + player1Name + ', welcome to tic-tac-toe ~ ')
playerScore = 0
CPUScore = 0
while theBoardChoices != None:
print(' ~ ' +player1Name + ', please type in one of the options for blank spaces: ~ ')
print('')
print(' , '.join(theBoardChoices.keys()))
playerInput = input()
playerChoice = playerInput.lower()
choiceCheck = theBoard.get(playerChoice,'bad choice')
if choiceCheck != 'bad choice':
if theBoard[playerChoice] == ' ':
theBoard[playerChoice] = 'X'
printBoard(theBoard)
del theBoardChoices[playerChoice]
if winCondition(theBoard) == 'win':
theBoardChoices = copy.deepcopy(theBoard)
playerScore = playerScore + 1
print(' ~ the current score is ' + str(playerScore) +'-' + str(CPUScore) + ' ~ ')
newGame(theBoardChoices)
continue
if theBoardChoices == {}:
theBoardChoices = copy.deepcopy(theBoard)
newGame(theBoardChoices)
continue
print(' ~ Please wait. Computer making a choice... ~ ')
time.sleep(random.randint(1,2))
computerChoice(theBoard)
if loseCondition(theBoard) == 'lose':
theBoardChoices = copy.deepcopy(theBoard)
CPUScore = CPUScore + 1
print(' ~ the current score is ' + str(playerScore) +'-' + str(CPUScore) + ' ~ ')
newGame(theBoardChoices)
continue
else:
print(' ~ that space is already taken ~ ')
My questions regarding this code are as follows:
My win and lose condition functions seem very bulky, but I couldn't think of another way of reducing that down or preferably making them into one function. I thought maybe I could make something that would be 'if these three keys values = x', and then if 'x = O then lose', or 'x = X then lose'. But I think my understanding of functions is still a bit cloudy. Is there a way to make a function that would generate that list of win/loss conditions using my set up? Or would I have had to made the board a list instead of a dictionary in order to use a more mathematical approach to reduce that?
I have a bunch of nested
if
's in my while statement. I feel like I could have made more use of functions to reduce that, but even so, I'm wondering if there are issues down the line of having that.I was planning on having a try/except in there, but by the time I reached the code that I have, I couldn't find a way to break it to make it active. Should I have put that in there any ways? I feel like always having that back up system there is a good habit, even if the code seems unbreakable. Or would that just make the code more messy and a waste of space?
In general, I know that old habits die hard, and that can be true of bad habits too. I would very much like to know if there are signs of bad things in my code that I can be wary of so I can nip it in the bud before I keep going. My plan is to revisit this code in the future and add to it (option of 2 human players or vs CPU or 2 CPU's face eachother, some AI of sorts, option of letting players set the size of the board, etc).
Anyways, I am looking forward to any feed back!
del
andcopy.deepcopy
is probably one of the bad habits that you don't want to develop. Usually you don't want or need this type of memory manipulation in Python. Instead of copying from a template, and then deleting from that copy, you would rather use a list that manages the remaining valid keys. For this you may want to learn the appropriate methods to remove elements from containers (e.g.pop()
for dictionaries orremove()
andpop()
for lists). \$\endgroup\$