Welcome to part 14 of the Python 3 basics series. Things are definitely cleaning up, we just have a few more things to conquer here. We need to stop players from playing over one another, we need to fix repetition in the win function, we need to fix the scalability of the column #s, and, finally, we need to actually return winners and either quit/restart the game!
To begin, let's solve the players playing over players issue. We just need to make sure the place the person intends to play is not currently occupied. That's do-able in a variety of ways. We'd like to let the user try again if the move is invalid. So far, we've been returning False
if the player tries something that they can't, so let's just continue that in the game_board
function. We can just add it near the top:
def game_board(game_map, player=0, row=0, column=0, just_display=False): try: if game_map[row][column] != 0: print("This space is occupied, try another!") return False
Okay, let's add some handling in the main loop for this:
while not game_won: current_player = next(player_cycle) played = False while not played: print(f"Player: {current_player}") column_choice = int(input("Which column? ")) row_choice = int(input("Which row? ")) played = game_board(game, player=current_player, row=row_choice, column=column_choice)
Let's test things up to this point now:
C:\Users\H\Desktop\python3-updated-series>py -3.6 part14.py 0 1 2 0 [0, 0, 0] 1 [0, 0, 0] 2 [0, 0, 0] Player: 1 Which column? 9 Which row? 9 Did you attempt to play a row or column outside the range of 0,1 or 2? (IndexError) Player: 1 Which column? 1 Which row? 1 0 1 2 0 [0, 0, 0] 1 [0, 1, 0] 2 [0, 0, 0] Player: 2 Which column? 1 Which row? 1 This space is occupied, try another! Player: 2 Which column?
Great, this seems to be working. Next up, let's fix the repeated "win" check bit.
First, let's just make a quick function:
def all_same(l): if l.count(l[0]) == len(l) and l[0] != 0: return True else: return False
This will go inside our win
function and then we can use this with everything here. Example:
def win(current_game): def all_same(l): if l.count(l[0]) == len(l) and l[0] != 0: return True else: return False # horizontal for row in game: print(row) if all_same(row): print(f"Player {row[0]} is the winner horizontally!")
Full win function becomes:
def win(current_game): def all_same(l): if l.count(l[0]) == len(l) and l[0] != 0: return True else: return False # horizontal for row in game: print(row) if all_same(row): print(f"Player {row[0]} is the winner horizontally!") # vertical for col in range(len(game[0])): check = [] for row in game: check.append(row[col]) if all_same(check): print(f"Player {check[0]} is the winner vertically!") # / diagonal diags = [] for idx, reverse_idx in enumerate(reversed(range(len(game)))): diags.append(game[idx][reverse_idx]) if all_same(diags): print(f"Player {diags[0]} has won Diagonally (/)") # \ diagonal diags = [] for ix in range(len(game)): diags.append(game[ix][ix]) if all_same(diags): print(f"Player {diags[0]} has won Diagonally (\\)")
We've not saved any lines. In fact, we added some, but this is much cleaner, and, let's say we changed something where no longer the value in the cell needed to not be 0, and instead maybe a space, or something like that. Now, we just need to change this in one place, rather than 4.
Now, let's actually return winners. Again, we can just use our win
function for this to either return a True
or False
. After each of the winning print outs, we can add a return True
. At the end, we return False
. Once a function reaches a return
, it's done running stuff. If we make it all the way to the end, that means no one won. Full function:
def win(current_game): def all_same(l): if l.count(l[0]) == len(l) and l[0] != 0: return True else: return False # horizontal for row in game: print(row) if all_same(row): print(f"Player {row[0]} is the winner horizontally!") return True # vertical for col in range(len(game[0])): check = [] for row in game: check.append(row[col]) if all_same(check): print(f"Player {check[0]} is the winner vertically!") return True # / diagonal diags = [] for idx, reverse_idx in enumerate(reversed(range(len(game)))): diags.append(game[idx][reverse_idx]) if all_same(diags): print(f"Player {diags[0]} has won Diagonally (/)") return True # \ diagonal diags = [] for ix in range(len(game)): diags.append(game[ix][ix]) if all_same(diags): print(f"Player {diags[0]} has won Diagonally (\\)") return True return False
Now, let's use the win()
function in our main loop, with something like:
if win(game): game_won = True again = input("The game is over, would you like to play again? (y/n) ") if again.lower() == "y": print("restarting!") elif again.lower() == "n": print("Byeeeee!!!") play = False else: print("Not a valid answer, but... c ya!") play = False
Full code now:
import itertools def win(current_game): def all_same(l): if l.count(l[0]) == len(l) and l[0] != 0: return True else: return False # horizontal for row in game: print(row) if all_same(row): print(f"Player {row[0]} is the winner horizontally!") return True # vertical for col in range(len(game[0])): check = [] for row in game: check.append(row[col]) if all_same(check): print(f"Player {check[0]} is the winner vertically!") return True # / diagonal diags = [] for idx, reverse_idx in enumerate(reversed(range(len(game)))): diags.append(game[idx][reverse_idx]) if all_same(diags): print(f"Player {diags[0]} has won Diagonally (/)") return True # \ diagonal diags = [] for ix in range(len(game)): diags.append(game[ix][ix]) if all_same(diags): print(f"Player {diags[0]} has won Diagonally (\\)") return True return False def game_board(game_map, player=0, row=0, column=0, just_display=False): try: if game_map[row][column] != 0: print("This space is occupied, try another!") return False print(" 0 1 2") if not just_display: game_map[row][column] = player for count, row in enumerate(game_map): print(count, row) return game_map except IndexError: print("Did you attempt to play a row or column outside the range of 0,1 or 2? (IndexError)") return False except Exception as e: print(str(e)) return False play = True players = [1, 2] while play: game = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] game_won = False player_cycle = itertools.cycle([1, 2]) game_board(game, just_display=True) while not game_won: current_player = next(player_cycle) played = False while not played: print(f"Player: {current_player}") column_choice = int(input("Which column? ")) row_choice = int(input("Which row? ")) played = game_board(game, player=current_player, row=row_choice, column=column_choice) if win(game): game_won = True again = input("The game is over, would you like to play again? (y/n) ") if again.lower() == "y": print("restarting!") elif again.lower() == "n": print("Byeeeee!!!") play = False else: print("Not a valid answer, but... c ya!") play = False
We're basically done, but we gotta fix the print(" 0 1 2")
bit to be dynamic.
How can we do this? We've seen that we can use +
with strings, and we know about loops. Let's just make a quick basic example. We're going to start with 3 spaces, then we need to add a number and 2 spaces between them. Let's see...
Since we need to start with 3 spaces, and we know that we want to have 2 spaces between each number, we can do something like start with 1 space, then use a for loop to add 2 spaces and a number per loop. Something like:
s = " " for i in range(3): s += " "+str(i) print(s)
The 3
in range can just be dervied from the game's size later.
There's also a string method to achieve this same task called .join
. Then we can use list comprehension, like:
s = " "+" ".join([str(i) for i in range(3)]) print(s)
If the list comprehension doesn't make sense to you, use our original example, that's fine!
So, either build s and print it in place of our original print there, or do:
print(" "+" ".join([str(i) for i in range(len(game_map))]))
That's it. We're done. Full code:
import itertools def win(current_game): def all_same(l): if l.count(l[0]) == len(l) and l[0] != 0: return True else: return False # horizontal for row in game: print(row) if all_same(row): print(f"Player {row[0]} is the winner horizontally!") return True # vertical for col in range(len(game[0])): check = [] for row in game: check.append(row[col]) if all_same(check): print(f"Player {check[0]} is the winner vertically!") return True # / diagonal diags = [] for idx, reverse_idx in enumerate(reversed(range(len(game)))): diags.append(game[idx][reverse_idx]) if all_same(diags): print(f"Player {diags[0]} has won Diagonally (/)") return True # \ diagonal diags = [] for ix in range(len(game)): diags.append(game[ix][ix]) if all_same(diags): print(f"Player {diags[0]} has won Diagonally (\\)") return True return False def game_board(game_map, player=0, row=0, column=0, just_display=False): try: if game_map[row][column] != 0: print("This space is occupied, try another!") return False print(" "+" ".join([str(i) for i in range(len(game_map))])) if not just_display: game_map[row][column] = player for count, row in enumerate(game_map): print(count, row) return game_map except IndexError: print("Did you attempt to play a row or column outside the range of 0,1 or 2? (IndexError)") return False except Exception as e: print(str(e)) return False play = True players = [1, 2] while play: game = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] game_won = False player_cycle = itertools.cycle([1, 2]) game_board(game, just_display=True) while not game_won: current_player = next(player_cycle) played = False while not played: print(f"Player: {current_player}") column_choice = int(input("Which column? ")) row_choice = int(input("Which row? ")) played = game_board(game, player=current_player, row=row_choice, column=column_choice) if win(game): game_won = True again = input("The game is over, would you like to play again? (y/n) ") if again.lower() == "y": print("restarting!") elif again.lower() == "n": print("Byeeeee!!!") play = False else: print("Not a valid answer, but... c ya!") play = False
Now, finally, if you desired, you ... cound allow users to dynamically size the game as well, so here's just a bonus. For example:
game_size = 5 game = [] for i in range(game_size): row = [] for i in range(game_size): row.append(0) game.append(row) print(game)
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
All set. The rest of our code should handle this just fine. We can also actually do the above in all 1 line, again using list comprehension:
game = [[0 for i in range(game_size)] for i in range(game_size)] print(game)
So, we can do the following in our loop:
play = True players = [1, 2] while play: game_size = int(input("What size game TicTacToe? ")) game = [[0 for i in range(game_size)] for i in range(game_size)]
Then we have a varying-sized game. Pretty nifty!
Okay, that's enough of the basics of Python syntax. We're *definitely* missing a lot of stuff. For example, a super integral bit to python is the dictionary
, which is a way to store values by keys, for example. It's just not come up yet, but it exists and is super useful. There are many other useful things like that, which we've just not covered.
That said, you should have a grasp of Python now.