Reputation: 57581
I'm writing a Python script which is supposed to allow human and computer players to play Tic Tac Toe. To represent the board, I'm using a 3x3 Numpy array with 1
and 0
for the marks of the players (instead of "X" and "O"). I've written the following function to determine the winner:
import numpy as np
class Board():
def __init__(self, grid = np.ones((3,3))*np.nan):
self.grid = grid
def winner(self):
rows = [self.grid[i,:] for i in range(3)]
cols = [self.grid[:,j] for j in range(3)]
diag = [np.array([self.grid[i,i] for i in range(3)])]
cross_diag = [np.array([self.grid[2-i,i] for i in range(3)])]
lanes = np.concatenate((rows, cols, diag, cross_diag))
if any([np.array_equal(lane, np.ones(3)) for lane in lanes]):
return 1
elif any([np.array_equal(lane, np.zeros(3)) for lane in lanes]):
return 0
So for example, if I execute
board = Board()
board.grid = np.diag(np.ones(3))
print board.winner()
I get the result 1
. What bothers me slightly is the repetition of the any
statements. I would think there would be a more concise, DRY way of coding this. (I was thinking of a switch/case as in MATLAB but this doesn't exist in Python). Any suggestions?
Upvotes: 2
Views: 251
Reputation: 94545
This can be done in two lines, starting from the board (grid
): simple sums along columns, rows and the two main diagonals gives you a value of 0 or 3 depending on who is winning (or some intermediate values only if nobody is winning). You can thus calculate something like:
# Score along each column, row and both main diagonals:
scores = (grid.sum(axis=0).tolist() + grid.sum(axis=1).tolist()
+[grid.trace(), np.flipud(grid).trace()])
# If there is no winner, None is declared the winner:
print "Winner:", 1 if 3 in scores else 0 if 0 in scores else None
where flipud()
transforms the diagonal into the anti-diagonal (the diagonal at 90° from the main diagonal) by flipping the array horizontally, so that a simple trace()
gives the total value along the anti-diagonal.
Upvotes: 0
Reputation: 8059
Another option is to check the sum of lanes
.
s = np.sum(lanes, axis=1)
if 3 in s:
return 1
elif 0 in s:
return 0
Upvotes: 2
Reputation: 140216
I have made a loop instead, and return only once, to conform with PEP8 and to be honest to my personal coding standards :)
enumerate
in the correct order will yield 0,zeromatrix
then 1,onematrix
rval = None
for i,m in enumerate([np.zeros(3),np.ones(3)]):
if any([np.array_equal(lane, m) for lane in lanes]):
rval = i; break
return rval
Upvotes: 1
Reputation: 57581
I found out one way, by using a Lambda function:
any_lane = lambda x: any([np.array_equal(lane, x) for lane in lanes])
if any_lane(np.ones(3)):
return 1
elif any_lane(np.zeros(3)):
return 0
This adds an extra line to the code but makes it more legible overall, I reckon.
Upvotes: 0