if 0 in self.board:
return None
There are board positions where a win is no longer possible but there still are open spaces. Continuing playing at this point is awkward, the game should stop. Therefore your class should be reporting such a board state as a draw.
One of the many such positions is this one:
|
x | o | x |
---|---|---|---|
o | x | o |
No matter what the players do at this point, there's no possible outcome other than draw.
The other answer already mentioned NumPy arrays. Using such an array for the board would make the code both faster and easier to read, because the indexing is that much easier. For example,
line_sum = (self.board[win_condition[0]]
+ self.board[win_condition[1]]
+ self.board[win_condition[2]])
would become
line_sum = np.sum(self.board[win_condition])
And actually you’d be able to get rid of the loop over the win condition array entirely.
I would speed up the code by writing a routine that iterates over all board positions, and generates a table that you can then include as a hard-coded table in your program. 39 is only 19,683 possible board positions. Taking symmetry into account this is a much smaller list. Now evaluating the board and finding the optimal move during the game is just a simple table lookup. This might not be necessary, it is likely that your code is fast enough for interactive play, but it’s an option.
I don’t know what code the @property
decorator adds, whether it adds to the cost of calling this function or not. I would profile your code again without this decorator.
I personally find it unnecessary to syntactically turn a function call into referencing a variable, it doesn’t seem to improve readability and just hides the fact that a function is being called. But this is just an opinion, it might be meaningful to you.