Reputation: 331
I am writing a Connect Four game in which you can choose the size of the board. The game works flawlessly for most board sizes but gives me problems when the board is taller then it is wide. I keep getting index out of range errors and I'm not sure what I have done wrong.
This is what I have right now in terms of my check function as it is the only part giving me issues.
def checkOWin(board):
boardHeight = len(board)
boardWidth = len(board[0])
tile = 'O'
# check horizontal spaces
for y in range(boardHeight):
for x in range(boardWidth - 3):
if board[x][y] == tile and board[x+1][y] == tile and board[x+2][y] == tile and board[x+3][y] == tile:
return True
# check vertical spaces
for x in range(boardWidth):
for y in range(boardHeight - 3):
if board[x][y] == tile and board[x][y+1] == tile and board[x][y+2] == tile and board[x][y+3] == tile:
return True
# check / diagonal spaces
for x in range(boardWidth - 3):
for y in range(3, boardHeight):
if board[x][y] == tile and board[x+1][y-1] == tile and board[x+2][y-2] == tile and board[x+3][y-3] == tile:
return True
# check \ diagonal spaces
for x in range(boardWidth - 3):
for y in range(boardHeight - 3):
if board[x][y] == tile and board[x+1][y+1] == tile and board[x+2][y+2] == tile and board[x+3][y+3] == tile:
return True
return False
Upvotes: 11
Views: 26280
Reputation: 41
I think the problem lies in the conditional.
Here is a sample input that should be similar to what OP is using:
board = [['_','_','_','_','_','_','_'],
['_','_','_','_','_','_','_'],
['_','_','_','X','_','_','_'],
['_','_','_','O','_','_','_'],
['_','X','X','O','O','O','O'],
['X','X','X','O','O','X','O']]
I've reworked the conditional to be:
for x in range(boardWidth):
for y in range(boardHeight):
try:
if board[y][x] == tile and board[y][x+1] == tile and board[y][x+2] == tile and board[y][x+3] == tile:
return True
except IndexError:
next
We have 6 different lists stored in the board list. The y comes first when accessing the board because it first tells us which list of those 6 we need to use, moving us up or down in the board list. Now the x index moves us within whichever y list we have accessed.
The try and except allow you to remove the for x in range(boardWidth - 3)
because if an index error is received, we know we've reached the edge of the game board and cannot meet the win condition. So we move on to the next point to test.
Upvotes: 4
Reputation: 753
Although the successive nested for loops are the obvious solution for win detection, it is a rather slow approach in a language such as python. The problem can actually be assimilated to a convolution operation on the two dimensions of the Connect 4 board, with convolution kernels designed to match horizontal, vertical and diagonal lines of 4 tiles.
A faster approach would thus be to:
Create kernels for horizontal, vertical and diagonal win detection.
horizontal_kernel = np.array([[ 1, 1, 1, 1]])
vertical_kernel = np.transpose(horizontal_kernel)
diag1_kernel = np.eye(4, dtype=np.uint8)
diag2_kernel = np.fliplr(diag1_kernel)
detection_kernels = [horizontal_kernel, vertical_kernel, diag1_kernel, diag2_kernel]
Create a 2D array from your board, in which all of a player's tiles are set to 1, and all empty/opponent tiles are set to 0.
Run the board through the convolution operations using SciPy's highly optimized convolve2d function.
In the array formed by the convolution output, any "4" indicates that there were 4 connected tiles in the board.
from scipy.signal import convolve2d
def winning_move(board, player):
for kernel in detection_kernels:
if (convolve2d(board == player, kernel, mode="valid") == 4).any():
return True
return False
This allows for a huge speed-up for detection of the winning conditions which is crucial when implementing tree-search like algorithms on the game tree. I also find this solution to be more elegant and readable.
Upvotes: 40
Reputation: 9969
You've just mixed up your dimensions, you should set them this way:
def checkOWin(board):
boardHeight = len(board[0])
boardWidth = len(board)
Because when you refer to board[x], that's counting the number of lists in the board, and when you refer to board[x][y] that's just referring to the length of one specific row in the board.
if board[x][y] == tile and board[x+1][y] == tile and board[x+2][y] == tile and board[x+3][y] == tile:
When I flipped those values the function ran without errors.
Upvotes: 7