Welcome to part 10 of the Python 3 basics series. Up to this point we've been building our TicTacToe game, and now we're ready to start figuring out how we can identify winners of the game. You may be thinking "but, players can't even play yet?!?" I know I know, we will add this, but this is really one of the last steps we need. Until then, we really can develop everything easier by just hard-coding in our tests. Again, here we "know" when a winner has actually won (3 in a row horizontally, vertically, or diagonally), but how can we define this in a program?
This looks like a problem for us to... break down!
The algorithm to determine these things is going to be different for horizontal, vertical and diagonals. So, let's start with the easiest one: horizontal.
Let's just start with an empty script to develop this. Let's just add a game board that looks like:
game = [[1, 1, 1], [2, 2, 0], [1, 2, 0]]
Clearly that top row won. How might we determine this? Well, we can just iterate over the rows first, then hard-code a check for each value. Something like:
game = [[1, 1, 1], [2, 2, 0], [1, 2, 0]] def win(current_game): for row in game: print(row) # how might we check all items in this row? We could do something like: column_1 = row[0] column_2 = row[1] column_3 = row[2] if column_1 == column_2 == column_3: print("WINNER!") win(game)
Okay, that works, but... on a new game... we'll wind up with a winner. We could also just check to make sure any one of the values isn't a zero:
if column_1 == column_2 == column_3 and column_1 != 0:
This is fine. This works, and that's often what matters most, whether you're trying to just get something done for yourself, or maybe you're trying to meet some deadline your boss set.
That said, like we've seen before, if you see repetition in code, chances are, there's a better way. Sometimes, people go way overboard, making super confusing "one-liners" that are nearly impossible to read. There's definitely a balance you want to strike here, because reading the above code is easy, it's just fairly clunky. Also, what happens when your boss decides they want a 4x4 tic tac toe? Or a 5x5? then back to a 3x3? Then she decides she wants it to be variable, choose-able by the user. Yikes! Our program is tiny in the grand scheme of things, but making these changes already sounds frustrating and super tedious! Plus, it will require a total re-work later if we needed to make this change, or maybe we do something that I've seen all too often with some of my clients: A unique script for each variable difference. Seriously, this is very commonly done, and this introduces an insane amount of what we call technical debt
to a project. The idea of making unique versions is fine, until you need to make a future change, maybe implement a bug fix, or something else, and now you need to modify 10s, hundreds, or even thousands of python scripts. Again, I've seen it in the thousands. You're going to need to probably write a python script to handle this!
When we make our code more dynamic from the start, it helps us down the road. It might be slightly more challenging initially, but it's usually worth it. The point is, this technical debt
stacks up over time as a project grows, causing more and more problems, and only compounding the amount of time it's going to take to actually make things right. It's almost never going to be the case that the code you write initially in a project will never be touched again, so you want to make it as easy as possible to return to it later.
Okay, so, how can we make this code dynamic? I can think of a few ways, and maybe you can too. What if you cant? Give up? NO!
What can we do here? Well, what's our real objective? We want to know if each of the rows in our game as all of the same #s in it, if those numbers are 1 or 2 (players), and not 0 (no move there). What are these rows? They're lists. So we want to find out if all of the items in a list are identical... and not zero. We're pretty certain we can do the "not zero" part, but what about the other part?
People always ask where I learn so much. Here's the secret:
Really.
So here, I just type in to google:
It's really that simple. Just figure out how to word your question as quickly as possible, end with "python" and boom, you've got your query! Let's see what we get:
Your best friend is Stack Overflow usually, and, sure enough, that's a top result there. The title is not the same as our query, but good ol' Google figured it out for us. Thanks Google!
Let's check out that top stack overflow (SO) example. Just in case your search didn't yield the same results, here's the link to this one specifically: check if all elements in a list are identical.
Alright, this is supposed to be a basics tutorial, what in the heck is all this code we see here?! People are building tables and talking about a bunch of things like speed tests and all that. The original poster (OP) said they didn't want the code to cause "unnecessary" overhead, so what has ensued here is a pissing contest about who can write the absolute fastest code to solve this. We're not looking to scale this out on some VPS to millions of users, we just want simple code that can grow in time. Thus, we probably don't need to get too crazy here. I see the 2nd-best answer by votes looks super simple:
x.count(x[0]) == len(x)
I think we can all read this and understand it. We're counting how many times the first value in the x
list, and then seeing if that value occurs as many times as the x
list is long. In order for this to be true, all of the values in the list would have to be the same. Alright, let's implement this:
game = [[1, 1, 1], [2, 2, 0], [1, 2, 0]] def win(current_game): for row in game: print(row) if row.count(row[0]) == len(row) and row[0] != 0: print("Winner!") win(game)
[1, 1, 1] Winner! [2, 2, 0] [1, 2, 0] >>>
So not only is this dynamic, it takes less code to do it. Now, our rows could be however long we wanted. Obviously we're hard-coding our game variable, and we have one more hard-coded value (the top row #s), which we will definitely handle before we're done here. We also should probably handle for who the winner is. We can just do that right now.
We can determine the winner by just printing out row[0]
def win(current_game): for row in game: print(row) if row.count(row[0]) == len(row) and row[0] != 0: print("Winner!") print(row[0])
output:
[1, 1, 1] Winner! 1 [2, 2, 0] [1, 2, 0] >>>
We can clean this up slightly with:
print("The winner is:", row[0])
[1, 1, 1] The winner is: 1 [2, 2, 0] [1, 2, 0] >>>
Also, we can use what's called string formatting, specifically with f strings
, to better format variables into strings. An f string is just a string that is built with f""
rather than ""
. Inside of an f string, you can pass variables inside of curly braces ({ and })
print(f"Player {row[0]} is the winner!")
[1, 1, 1] Player 1 is the winner! [2, 2, 0] [1, 2, 0] >>>
Alright, we've got a way to check for horizontal victories. Now we'll conquer the vertical victories in the next tutorial.