Darshan Pandit
Darshan Pandit

Reputation: 178

Better design to evaluate Goal States for Tic-Tac-Toe using Numpy

I am trying to build a m*n Tic-Tac-Toe using numpy to maintain my game-state. To evaluate a win, we need atleast 'q' continuous blocks of an element in either row, columns or along the diagonals. (3 in case of the original tic-tac-toe).

One crude approach I have currently implemented currently is to generate all the row, column and vectors along the diagonal path and then iterate over each of them to find a continious block of 'q' using iteration.

I would want to optimize it further. Since, I am relatively new to working with numpy and computation using matrices, I want to know if we can do it in any better fashion, more specifically, if I can better leverage numpy functionalities to do some more of these computations.

Upvotes: 2

Views: 876

Answers (2)

leewz
leewz

Reputation: 3346

You can use slices instead of generation. A numpy array can be indexed like so (see https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html):

arr[r] # Row r
arr[:,c] # Every row, c'th column
np.diagonal(arr) # Diagonal
# Note: No easy way to get the antidiagonal.

You can check all elements of a numpy array with ==, which generates an array of answers.

all(arr[r] == 'X') # Does not short-circuit!
all(cell == 'x' for cell in arr[r]) # Stops at first failure.

Better optimization: You only need to check lines where the last piece was placed, assuming you check after each move. And you only need to check for the last player's piece (X or O).

Upvotes: 3

Ashwini Chaudhary
Ashwini Chaudhary

Reputation: 250991

Lets say 1 belongs to Player1, 2 belongs to Player 2 and 0 means empty spaces. Then for each row/column/diagonal in the array you can find out the count of 1 and 2, and if it equals the length of row then it means a Player has already won. Here using numpy.apply_along_axis I've applied a function to each row/column/diagonal in the array and it returns the count of 1 or 2 in that row divided by length of that row, so if for any case 1 is returned then it means that row/column/diagonal contains either all 1's or 2's.

Demo:

from functools import partial
import numpy as np

def func(x, a):

    return len(a[a==x])/len(a)

def check_solved(a):

    """
    0: Empty places
    1: Player 1
    2: Player 2
    """

    player_1 = partial(func, 2)
    player_2 = partial(func, 1)
    data_list = [a, a.T, a.diagonal(), np.flipud(a).diagonal()]
    if any(np.apply_along_axis(f, 0, d).any() for d in data_list for f in [player_1, player_2]):
        print "Game Over"
    else:
        print "Not over yet"

Demo:

>>> x = array([[1, 1, 0, 0, 2],
       [0, 0, 2, 2, 2],
       [1, 1, 1, 1, 1],
       [1, 2, 2, 0, 2],
       [2, 0, 2, 2, 1]])
>>> check_solved(x)
Game Over

>>> x = array([[2, 0, 0, 1, 0],
...        [0, 2, 2, 0, 1],
...        [1, 1, 2, 0, 0],
...        [2, 2, 0, 2, 0],
...        [0, 0, 1, 1, 2]])
>>> check_solved(x)
Game Over

>>> x = array([[0, 0, 1, 1, 1],
       [1, 1, 0, 2, 2],
       [0, 0, 1, 0, 1],
       [0, 0, 2, 1, 2],
       [2, 0, 2, 0, 0]])
>>> check_solved(x)
Not over yet

Upvotes: 2

Related Questions