Determining three in a row in Python 2d array - python

I'm working on a tic-tac-toe game with a M x N board in Python. I'm trying to find an efficient way to determine if a player has won (3 in a row either vertical, horizontal, or diagonal direction.) Most 3x3 implementations of the game just check for all possible winning combinations after each turn. This seems a little extreme with a massive board.
4x4 example: (using 1s and 2s instead of Xs and Os)
board = ([1,0,2,1], [0,0,0,1], [2,2,0,0], [1,0,0,1])
for row in board:
print row
Thanks-
Jonathan

Although this approach has a certain appeal, it's probably not especially fast.
# A bogus game with wins in several directions.
board = (
[1,1,2,1],
[0,2,1,1],
[2,2,2,1],
[1,0,0,1],
)
# A few convenience variables.
n_rows = len(board)
lft = [ [0] * i for i in range(n_rows) ] # [[], [0], [0, 0], [0, 0, 0]]
rgt = list(reversed(lft))
# Create transpositions of the board to check for wins in various directions.
transpositions = {
'horizontal' : board,
'vertical' : zip(*board),
'diag_forw' : zip(* [lft[i] + board[i] + rgt[i] for i in range(n_rows)] ),
'diag_back' : zip(* [rgt[i] + board[i] + lft[i] for i in range(n_rows)] ),
}
# Apply Jonathan's horizontal-win check to all of the transpositions.
for direction, transp in transpositions.iteritems():
for row in transp:
s = ''.join( map(str, row) )
for player in range(1,3):
if s.find(str(player) * 3) >= 0:
print 'player={0} direction={1}'.format(player, direction)
Output:
player=1 direction=diag_back
player=2 direction=diag_forw
player=2 direction=horizontal
player=1 direction=vertical
The idea behind the diagonal transpositions is to shift the rows, using lft and rgt for left and right padding. For example, the diag_forw list looks like this after the padding is added (pad characters are shown as periods, even though zeroes are used in the actual code).
1 1 2 1 . . .
. 0 2 1 1 . .
. . 2 2 2 1 .
. . . 1 0 0 1
Then we simply transpose that array, using zip(*foo), which allows us to use Jonathan's good idea for finding horizontal wins.

You can look if the player's move closed the game (looking on that row, that column and the 2 diagonals if they ar x checks consecutively), it's o(x) complexity. Let's say you're looking o that row to see if he won. Look to the left how many consecutively checks are and to the right. If the sum of them excedes x he won. You'll do the same on the columns and on the diagonals.

Check for horizontal win
for row in board:
rowString = ''.join(row)
if(rowString.count('111') > 2 or rowString.count('222') > 2):
print "Somebody won"
Check for vertical win
for col in xrange(len(board[0])):
colString = ""
for row in board:
colString = colString.append(row[col])
if(colString.count('111') > 2 or colString.count('222') > 2):
print "Somebody won"
Still stumped on diagonals...

If you have a board set up as follows:
board =
([1,0,2,0],
[0,1,2,0],
[0,0,0,0],
[0,0,0,0])
You can imagine it as x and y coordinates, starting from the upper left hand corner, having downward movement as positive y and rightward movement as positive x. A move at board[3][3] by either player would be a winning move. Using Teodor Pripoae process, we can construct the horizontal, vertical and diagonals around the last move. The horizontal case is easy.
def horizontal(board, y_coord):
return board[y_coord]
The vertical case requires us to select the x_coord from each row:
def vertical(board, x_coord):
return [row[x_coord] for row in board]
The diagonal case is a bit trickier. For this first function, it's computing the diagonal that goes from left to right as it goes top to bottom. Distance basically represents the horizontal distance from zero, when y is equal to zero.
def diagonal1(board, x_coord, y_coord):
length = len(board[0])
distance = x_coord - y_coord
if distance >= 0:
return [y[x] for x, y in enumerate(board) if distance + x <= length]
else:
return [y[x] for x, y in enumerate(board) if x - distance >= 0 and x - distance <= length]
This second function computes the diagonal that goes right to left as it goes top to bottom. In this function distance represents the vertical distance from zero as the horizontal distance is at zero.
def diagonal2(board, x_coord, y_coord):
length = len(board[0])
distance = y_coord + x_coord
return [y[distance - x] for x, y in enumerate(board) if distance - x <= length]
Once you have these defined, you just need a way to check if a player has won. Something like this might do:
def game_over(direction, number_to_win, player_number):
count = 0
for i in direction:
if i == player_number:
count += 1
if count = number_to_win:
return True
else:
count = 0
return False
Having written all of this, it seems like this is overkill, unless you have quite large M and N. While it may be more efficient than checking every victory condition, it does construct the entire horizontal, vertical and diagonal directions, rather than just those coordinates surrounding the last move, it isn't as efficient as it could be.
Maybe this is helpful, but it seems like Brian's suggestion to simply remove x's might be better.

I've been using a variant of this question in software developer interviews, so I've thought about the problem a fair bit. Here's a better answer: it handles any number of players, any square tic-tac-toe grid, and any "run size". The approach is fairly simple, provides info about all of the sequences found, and is O(N) where N is the number of cells.
# Given a square tic-tac-toe grid of any size, with any number of players, find
# all sequences (horizontal, vertical, diagonal) of some minimum size.
def main():
raw_grid = [
[1, 1, 2, 1, 0], # Zero means open spot.
[0, 2, 1, 1, 1],
[2, 2, 2, 1, 2],
[1, 0, 1, 1, 2],
[1, 0, 0, 0, 2],
]
for run in get_runs(raw_grid, 3):
print run
def get_runs(raw_grid, run_size):
# Offsets to find the previous cell in all four directions.
offsets = {
'h' : ( 0, -1), # _
'v' : (-1, 0), # |
'f' : (-1, 1), # /
'b' : (-1, -1), # \
}
# Helpers to check for valid array bounds and to return a new cell dict.
size = len(raw_grid)
in_bounds = lambda r, c: r >= 0 and c >= 0 and r < size and c < size
new_cell = lambda i, j, p: dict(h=1, v=1, f=1, b=1, i=i, j=j, player=p)
# Use the raw grid to create a grid of cell dicts.
grid = []
for i, row in enumerate(raw_grid):
grid.append([])
for j, player in enumerate(row):
# Add a cell dict to the grid (or None for empty spots).
cell = new_cell(i, j, player) if player else None
grid[i].append(cell)
if not cell: continue
# For each direction, look to the previous cell. If it matches the
# current player, we can extend the run in that direction.
for d, offset in offsets.iteritems():
r, c = (i + offset[0], j + offset[1])
if in_bounds(r, c):
prev = grid[r][c]
if prev and prev['player'] == cell['player']:
# We have a match, so the run size is one bigger,
# and we will track that run in the current cell,
# not the previous one.
cell[d] = prev[d] + 1
prev[d] = None
# For all non-None cells, yield run info for any runs that are big enough.
for cell in (c for row in grid for c in row if c):
for d in offsets:
if cell[d] and cell[d] >= run_size:
yield dict(
player = cell['player'],
endpoint = (cell['i'], cell['j']),
direction = d,
run_size = cell[d],
)
main()
Output:
{'player': 1, 'direction': 'h', 'endpoint': (1, 4), 'run_size': 3}
{'player': 2, 'direction': 'f', 'endpoint': (2, 0), 'run_size': 3}
{'player': 2, 'direction': 'h', 'endpoint': (2, 2), 'run_size': 3}
{'player': 1, 'direction': 'b', 'endpoint': (2, 3), 'run_size': 3}
{'player': 1, 'direction': 'f', 'endpoint': (3, 2), 'run_size': 3}
{'player': 1, 'direction': 'v', 'endpoint': (3, 3), 'run_size': 4}
{'player': 2, 'direction': 'v', 'endpoint': (4, 4), 'run_size': 3}

Related

Recurive Backtracking: Leet Code - Remove Boxes (Python)

I was working on this leetcode: https://leetcode.com/problems/remove-boxes/ and my answer is only slightly off for certain test cases. Any advice would be appreciated.
The problem is outlined as the following:
You are given several boxes with different colors represented by different positive numbers.
You may experience several rounds to remove boxes until there is no box left. Each time you can choose some continuous boxes with the same color (i.e., composed of k boxes, k >= >1), remove them and get k * k points.
Return the maximum points you can get.
Example 1:
Input: boxes = [1]
Output: 1 => (1*1)
Example 2:
Input: boxes = [1,1,1]
Output: 9 => (3*3)
Example 3:
Input: boxes = [1,3,2,2,2,3,4,3,1]
Output: 23
Explanation:
[1, 3, 2, 2, 2, 3, 4, 3, 1]
----> [1, 3, 3, 4, 3, 1] (3*3=9 points)
----> [1, 3, 3, 3, 1] (1*1=1 points)
----> [1, 1] (3*3=9 points)
----> [] (2*2=4 points)
I decided to use recursive backtracking to try and solve this, and my code is the following:
from copy import deepcopy as copy
class Solution:
# Main function
def backtrack(self, boxes, score, seen={}):
# Make list hashable
hashable = tuple(boxes)
if len(boxes) == 0:
return score
if hashable in seen:
return seen[hashable]
pos_scores = []
loop_start = 0
loop_end = len(boxes)
while(loop_start < loop_end):
# keep original boxes for original
box_copy = copy(boxes)
# Returns the continous from starting point
seq_start, seq_end = self.find_seq(box_copy, loop_start)
# Return the box array without the seqence, and the score from removal
new_boxes, new_score = self.remove_sequence(box_copy, seq_start, seq_end)
# Backtrack based off the new box list and new score
pos_scores.append(self.backtrack(box_copy, score+new_score, seen))
# Next iteration will use a fresh copy of the boxes
loop_start = seq_end
seen[hashable] = max(pos_scores)
return seen[hashable]
def remove_sequence(self, boxes, start, end):
rem_counter = 0
for i in range(start, end):
boxes.pop(i - rem_counter)
rem_counter += 1
dist = (end - start)
score = dist * dist
return boxes, score
def find_seq(self, boxes, start):
color = boxes[start]
end = start
for i in range(start, len(boxes)):
if boxes[i] == color:
end += 1
else:
break
return start, end
def removeBoxes(self, boxes) -> int:
return self.backtrack(boxes, 0, {})
My issue is that my code has worked for smaller examples, but is slightly off for the larger ones. I believe my code is almost there, but I think I'm missing an edge case. Any tips would be greatly appreciated. For example, I get the correct answer for [1,1,2,1,2] as well as most test cases. However my answer for the third example is 21, not 23.
Per #Armali's comment, the solution to the code above is to use
hashable = tuple(boxes), score

Bug with trying to count the no. of moves it takes to get from point A to point B on a grid. Not working for all values

I'm working on a challenge to count the number of moves it takes to get from point A to point B on a grid which is set out like a chessboard and the moves you can make are that of the Knight so 2 in any direction and 1 perpendicular.
I've gotten most of it worked out but for some reason, my counter is not returning the number of moves between the two points. Below is what I have regarding the counting.
You'll notice I'm using a dict called position and the reason for this is so that I can store an int representing the no of moves that particular position is from the destination.
I thought at the end I should be incrementing the move value after a move is deemed valid but I'm still failing to get the right number.
def solution(src, dest):
# Chessboard made using nested lists. The indexes will act as coordinates.
chessboard = [
[0,1,2,3,4,5,6,7],
[8,9,10,11,12,13,14,15],
[16,17,18,19,20,21,22,23],
[24,25,26,27,28,29,30,31],
[32,33,34,35,36,37,38,39],
[40,41,42,43,44,45,46,47],
[48,49,50,51,52,53,54,55],
[56,57,58,59,60,61,62,63]
]
# Find index values of src and dest
for row in chessboard:
if src in row:
srcX = chessboard.index(row)
srcY = row.index(src)
if dest in row:
destX = chessboard.index(row)
destY = row.index(dest)
# Position dict to store indexes and no of mvoes when using bfs
position = {
'x': 0,
'y': 0,
'moves': 0,
}
position['x'] = srcX
position['y'] = srcY
# Below represents the knights moves to be applied to the index of position
row = [-2,-2,-1,1,2,2,1,-1]
col = [-1,1,2,2,-1,1,-2,-2]
# We use an if-statement to check for valid moves
def isValid(x, y):
return not (x < 0 or y < 0 or x >=8 or y >=8)
q = []
q.append(position)
# Record spaces visited already
isVisited = []
while len(q)>0:
space = q.pop()
x = space['x']
y = space['y']
moves = space['moves']
# if the position matches the destination, return no.moves
# I'm just using print to see the result in the terminal
if x == destX and y == destY:
print(moves)
if (x,y) not in isVisited:
isVisited.append((x,y))
# Loop over possible moves
for i in range(len(row)):
newX = x + row[i]
newY = y + col[i]
if isValid(newX, newY):
position['x'] = newX
position['y'] = newY
position['moves'] = moves+1
q.append(position)
This is a common Python issue. You create a dictionary called "position" and add it to the queue. Then, any time you have a new move, you MODIFY that same dictionary and add it to the queue again, but that's not creating a NEW dictionary. What you will end up with is a queue that full of references to the exact same dictionary. Every time you change "position", you're changing every dict in the queue. You need to create a new object each time.
This seems to work.
# We use an if-statement to check for valid moves
def isValid(x, y):
return (0 <= x <= 7) and (0 <= y <= 7)
def solution(src, dest):
# Chessboard made using nested lists. The indexes will act as coordinates.
chessboard = [
[0,1,2,3,4,5,6,7],
[8,9,10,11,12,13,14,15],
[16,17,18,19,20,21,22,23],
[24,25,26,27,28,29,30,31],
[32,33,34,35,36,37,38,39],
[40,41,42,43,44,45,46,47],
[48,49,50,51,52,53,54,55],
[56,57,58,59,60,61,62,63]
]
# Find index values of src and dest
srcY = src % 8
srcX = src // 8
destY = dest % 8
destX = dest // 8
# Position dict to store indexes and no of mvoes when using bfs
position = {
'x': srcX,
'y': srcY,
'moves': 0,
}
# Below represents the knights moves to be applied to the index of position
row = [ -2, -2, -1, -1, 1, 1, 2, 2]
col = [ -1, 1, 2, -2, -2, 2, -1, 1]
q = []
q.append(position)
# Record spaces visited already
isVisited = []
while q:
space = q.pop()
print( "Checking", space )
x = space['x']
y = space['y']
moves = space['moves']
# if the position matches the destination, return no.moves
# I'm just using print to see the result in the terminal
if x == destX and y == destY:
print(f"{moves}!!!")
return moves
if (x,y) not in isVisited:
isVisited.append((x,y))
# Loop over possible moves
for dx,dy in zip(row,col):
newX = x + dx
newY = y + dy
if isValid(newX, newY):
position = {
'x': newX,
'y': newY,
'moves': moves+1
}
q.append(position)
print( solution( 3, 61 ) )

How can I make a loop to perform action on Neighbours of my start box in python?

I have made a grid using for loop and pygame :
and I have also connected it to a 2d-array. I have converted x, y into rows and columns according to the blocks.
now the green block represents the start position and I have to perform actions on its neighbors i.e all blocks touching it(even the corner blocks)
I wanted to know is there a more efficient solution to this as currently I am just storing them one-by-one in a list and then perform actions on it.
Like a loop or something like that.
and to get the row and column of each it takes a lot of code so please help I am a beginner.
Till now I have done something like this(I know this is the worst way)
self.neighboursx.append(self.start_point_column)
self.neighboursy.append(self.start_point_row - 1)
self.neighboursx.append(self.start_point_column + 1)
self.neighboursy.append(self.start_point_row - 1)
self.neighboursx.append(self.start_point_column + 1)
self.neighboursy.append(self.start_point_row)
self.neighboursx.append(self.start_point_column - 1)
self.neighboursy.append(self.start_point_row + 1)
self.neighboursx.append(self.start_point_column)
self.neighboursy.append(self.start_point_row + 1)
self.neighboursx.append(self.start_point_column - 1)
self.neighboursy.append(self.start_point_row + 1)
Create a list of the neighbour indices:
neighbours = [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)]
respectively
neighbours = [(i, j) for i in range(3) for j in range(3) if i != j]
And use the indices in a for loop:
for x, y in neighbours:
col = self.start_point_column + x
row = self.start_point_row + y
if 0 <= col < columns and 0 <= row < rows:
# do something with "row" and "col"
To loop through a 2D array, you could do something like:
for col in range(number_of_columns:
for row in range(number_of_rows):
do_something(col, row)
For your neighbour checking function:
def do_something(col, row): # e.g. count neighbours of a boolean 2D array dd
count = 0
for x in range(-1, 2): # -1, 0, 1
for y in range(-1, 2):
try:
if dd[col +x][row + y]:
count += 1
except IndexError: # cell is outside array
pass
# but we don't want to count ourselves,
if dd[col][row]:
count -= 1
# now do something with the count
…
Additionally, welcome to Stack Overflow. Please take the tour and read about How to Ask. Understanding this will make it easier to get helpful answers.

python game 2048 out of list index

I was working on the coursera python project 2048 using codesculpter.
The code works fine when I try 4 x 4 or 5 x 5, but it shows error when 4 x 5 or any other when height != width. I think I must have messed up somewhere in the __init__ or other places but I couldn't figure out.
Could someone give me some suggestions?
Here is what I have tried so far:
import poc_2048_gui
import random
# Directions, DO NOT MODIFY
UP = 1
DOWN = 2
LEFT = 3
RIGHT = 4
# Offsets for computing tile indices in each direction.
# DO NOT MODIFY this dictionary.
OFFSETS = {UP: (1, 0),
DOWN: (-1, 0),
LEFT: (0, 1),
RIGHT: (0, -1)}
def merge(line):
"""
Helper function that merges a single row or column in 2048
"""
# creat output list and remove 0
after_merge=[]
storage = []
for num_1 in range(len(line)):
after_merge.append(0)
if line[num_1] != 0 :
storage.append(line[num_1])
# sum number
for num_2 in range(len(storage)):
if num_2+2> len(storage):
break
elif storage[num_2]==storage[num_2+1]:
storage[num_2]*=2
storage.pop(num_2+1)
# replace 0 in after merge
for num in range(len(storage)):
after_merge[num]=storage[num]
return after_merge
class TwentyFortyEight:
"""
Class to run the game logic.
"""
def __init__(self, grid_height, grid_width):
self.grid_height = grid_height
self.grid_width = grid_width
self.cell=[]
self.indices = {}
self.indices[UP] = [[0,n] for n in range(grid_width)]
self.indices[LEFT] = [[n,0] for n in range(grid_height)]
self.indices[RIGHT] = [[n, grid_width - 1] for n in range(grid_height)]
self.indices[DOWN] = [[grid_height - 1, n]for n in range(grid_width)]
self.ranges = {}
self.ranges[UP] = grid_height
self.ranges[DOWN] = grid_height
self.ranges[LEFT] = grid_width
self.ranges[RIGHT] = grid_width
#self.reset()
def reset(self):
"""
Reset the game so the grid is empty except for two
initial tiles.
"""
self.cell = [[0*(col+row) for row in range(self.grid_height)] for col in range (self.grid_width)]
for count in range(2):
self.new_tile()
def __str__(self):
"""
Return a string representation of the grid for debugging.
"""
a_str = ""
for row in range(self.grid_height):
for col in range (self.grid_width):
a_str += ( str(self.cell[row][col]) + " " )
a_str += '\n'
return a_str
def get_grid_height(self):
"""
Get the height of the board.
"""
# replace with your code
return self.grid_height
def get_grid_width(self):
"""
Get the width of the board.
"""
# replace with your code
return self.grid_width
def move(self, direction):
"""
Move all tiles in the given direction and add
a new tile if any tiles moved.
"""
a_list = []
has_moved = False
for index in self.indices[direction]:
for step in range(self.ranges[direction]):
a_list.append(self.cell[index[0] + OFFSETS[direction][0] * step]
[index[1] + OFFSETS[direction][1] * step])
merged_list = merge(a_list)
if merged_list != a_list:
for step in range(self.ranges[direction]):
self.cell[index[0] + OFFSETS[direction][0] * step] \
[index[1] + OFFSETS[direction][1] * step] = merged_list[step]
has_moved = True
a_list = []
if has_moved:
self.new_tile()
def new_tile(self):
"""
Create a new tile in a randomly selected empty
square. The tile should be 2 90% of the time and
4 10% of the time.
"""
# replace with your code
row=0
col=0
available_positions = []
for row in range(self.grid_height):
for col in range(self.grid_width):
if self.cell[row][col] == 0:
available_positions.append([row, col])
if not available_positions:
print "There are no available positions."
random_pos=random.choice(available_positions)
rand_val=random.randint(1,10)
if rand_val>=9:
new_tile=4
else:
new_tile=2
self.set_tile(random_pos[0], random_pos[1], new_tile)
def set_tile(self, row, col, value):
"""
Set the tile at position row, col to have the given value.
"""
# replace with your code
self.cell[row][col] = value
def get_tile(self, row, col):
"""
Return the value of the tile at position row, col.
"""
# replace with your code
return self.cell[row][col]
poc_2048_gui.run_gui(TwentyFortyEight(4, 4))
Okay, I didn't debug all the way but here's what I found. cell should have dimension grid_height * grid_width. Correct?
However, right before this loop:
for row in range(self.grid_height):
for col in range(self.grid_width):
print(row," ",col);
if self.cell[row][col] == 0:
available_positions.append([row, col])
if not available_positions:
print "There are no available positions."
I found that the size of cell is reverse. That is grid_width * grid_height. Put these two lines before the nested loops to see for yourself.
print("cell size",len(self.cell)," ",len(self.cell[0]))
print("grid size",self.grid_height," ",self.grid_width)
This will cause IndexError in line if self.cell[row][col] == 0: when the dimensions are different. That being said, you should step through and see exactly how you fill in both the grid and the cell. Make sure they correspond correctly.
Hope that helps!
I did a quick debug and I was able to get a IndexError when calling move(). Looking through, you seem to expect self.cell to be populated, but it only is ever populated through your reset() function. You may not see this if your UI module calls reset when initializing...
There is a second IndexError then when the row and col are not the same number. This (as mentioned in the other answer) is because your 2D array representation is col * row, not row * col
below is the printout of (4, 6), which has 4 COLUMN and 6 ROW. You likely just need to swap the two in your representation:
[0, 0, 0, 0]
[2, 0, 2, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
A potential improvement to your syntax, but you can initiate your cells as such (test with your usage...)
self.cell = [[[0] * self.grid_width] for row in xrange(self.grid_height)]
Lastly, I believe you may get an IndexError in your new_tile because Python lists begin at the 0th element. You'll want to iter 0 through n-1:
for row in range(self.grid_height-1):
for col in range(self.grid_width-1):

Connect 4 Game logic

am developing http://en.wikipedia.org/wiki/Connect_Four game in python using tkinter.
I have come up with the board and the two player pieces. I am now trying to check if the game is over. I have implemented the following logic, but this doesnt seem to work.
def checkWin():
for row in range(canvas.data.rows):
for col in range(canvas.data.cols):
checkWinFromCell(row, col)
def checkWinFromCell(row, col):
if canvas.data.board[row][col] == 0:
return False
dirs = [[0,1], [1,0], [1,1], [1,-1], [0,-1], [-1,0], [-1,-1], [-1,1]]
for direction in dirs:
checkWinFromCellInDir(row, col, direction)
return False
def checkWinFromCellInDir(row, col, direction):
drow, dcol = direction[0], direction[1]
for i in range(1,4):
if row+i*drow<0 or row+i*drow>=canvas.data.rows or col+i*dcol<0 or col+i*dcol>=canvas.data.cols:
return False
if canvas.data.board[row][col] != canvas.data.board[row+i*drow][col+i*dcol]:
return False
return canvas.data.board[row][col]
I need to know the logic to check if my game has been completed ie the four dots have been connected.
I'm not very familiar with Tkinter, so this is a halfhearted answer at best. However since it's been nearly an hour and no answer is forthcoming, I did work one up for you.
class Map(list):
def __init__(self, tiles, width, height):
"""This object functions exactly as a tile map for your connect four
game. It is a subclass of list, so you can iterate through its rows.
"y" increases from top to bottom and "x" increases from left to right"""
for y in range(height):
self.append([random.choice(tiles) for x in range(width)])
# for ease of use, we're generating a random tile map here
def __str__(self):
return '\n'.join([' '.join([ch for ch in row]) for row in self])
# this will make print(map_object) show something pretty
Vector = collections.namedtuple("Vector", ['x','y'])
# build a namedtuple to contain our directions. It's a bit easier on the eyes
# to use object access, IMO. YMMV.
def checkwin(map_):
directions = [Vector(x, y) for (x, y) in [(1, 0), (-1, 1), (0, 1), (1, 1)]]
# directions = E, SW, S, SE
for y, row in enumerate(map_):
for x, ch in enumerate(row):
value = ch
if value == ' ': continue # blank squares can't win
for vector in directions:
result = checkfour(map_, x, y, vector)
if result:
return result
return False
def checkfour(map_, x, y, vector):
"""Checks map_ for four squares from the given x and y in vector direction"""
value = map_[y][x]
try:
lst = [map_[y + k*vector.y][x + k*vector.x]==value for k in range(1,4)]
# 2 2 2 1 would return [True, True, False]
return all(lst) and (x,y)
except IndexError:
return False
# we know we'll run off the edge of the map. It's cheaper (in programmer
# time) to simply return False here rather than check each square to make
# sure there ARE 3 more squares in vector-direction.
map_ = Map("12 ", 8, 8)
print(checkwin(map_))
# if your randomly generated map would win in connect four, it should print
# the first (x,y) coordinate that begins a win going E, SW, S, or SE
print(map_)

Categories

Resources