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_)
Related
Consider:
import numpy as np
def actions(state):
# Returns the indices of all blank spaces on the board (represented by '.')
return [i for i,s in np.ndenumerate(state) if s=='.']
def result(state, action, player):
# Returns a new state with the 'action' space taken by 'player'
new_state = state.copy() # Don't modify the passed-in array!
new_state[action] = player
return new_state
def is_terminal(state, k):
# Test whether 'state' is a terminal state or not
# Also return the final game score if yes
num_blanks = np.count_nonzero(state=='.')
# If X has k-in-a-row from any position, this is a terminal state
X_indices = [i for i,s in np.ndenumerate(state) if s=='X']
if has_k_in_a_row(X_indices, k):
return True, 1+num_blanks
# If O has k-in-a-row from any position, this is a terminal state
O_indices = [i for i,s in np.ndenumerate(state) if s=='O']
if has_k_in_a_row(O_indices, k):
return True, -(1+num_blanks)
# If there are no blanks left, the game ends with a tie
if num_blanks == 0:
return True, 0
# Otherwise, the game is not over
return False, None
#--------------------------------------------
# Helper functions used by is_terminal() (above)
def has_k_in_a_row(indices, k):
# Test whether there are k consecutive indices in a row in the given list of indices
# Get the indices as a set, for efficient subset testing
index_set = set(indices)
# For each starting position...
for start_pos in indices:
# Determine the length-k sequence of indices (starting at the current position)
# in each of four possible directions
winning_sequences = sequences(start_pos, k)
# If we have any of these sequences covered, we have 'k in a row'
if any([seq.issubset(index_set) for seq in winning_sequences]):
return True
# If we get here, we don't have 'k in a row'
return False
def sequences(start_pos, k):
# Return the 4 sets of k indices 'in a row' starting from index start_pos
# A win can be down, across, diagonally down, or diagonally up
across = set([(start_pos[0], start_pos[1]+j) for j in range(k)])
down = set([(start_pos[0]+j, start_pos[1]) for j in range(k)])
diagdown = set([(start_pos[0]+j, start_pos[1]+j) for j in range(k)])
diagup = set([(start_pos[0]+j, start_pos[1]-j) for j in range(k)])
# Return all 4 sets of indices
return across, down, diagdown, diagup
Review the code above, and make sure you understand it, especially actions, result, and is_terminal.
(Note: we could have gone with the standard ±1 utility for an X win/loss, but the extra "bonus" provides some useful information.)
We will be working with graphs, and two players. In this connected graph, the winning condition is that the second player has no other paths to take. The catch is that once a path is taken by a player, it can't be taken again.
Let us assume the initial input is adjacency list (x,y) means x has path to y
The goal is to return a set of vertices that player 1 can choose such that it will always win.
For example, if I have [(1,2), (2,0), (0, 3), (3,2)] and player 1 starts, then we should return [1, 0, 3]. We cannot return 2:
2 --> player 1 starts here
(2,0) --> player 2 goes to 0
(0,3) --> player 1 goes to 3
(3,2) --> player 2 goes to 2
(2,0) --> player 1 cannot go here, already taken
already_visited = []
turn = 1
result = []
def findStarting(L):
global already_visited
global turn
global result
for x,y in L:
allowed = can_visit(L, y) # function tell me which I can visit safely
turn = (turn % 2) + 1 # increment the turn
already_visited.append((x,y)) # we visited this edge
res = findStarting([(x, y)]) # recursive call (search on this node for paths)
if (turn == 2): return True
def can_visit(L, y):
res = []
for a,b in L: if (a==y and (a,b) not in already_visited): res.append((a,b))
return res
I am having trouble with the recursive case. I think what I want to do is return True if we reach a point where turn is 2 and the player has no paths they can take, but I am not sure how to move ahead from here
Here is a simple recursive solution. It is not efficient, it's brute force search without any caching of intermediate states, so it can definitely be made faster, though I don't know if there is an efficient (i.e. non-exponential) solution to this problem.
def firstPlayerWins(g,v):
for i,e in enumerate(g):
if e[0]==v and not firstPlayerWins(g[:i]+g[i+1:],e[1]):
return True
return False
def winningVertices(g):
return [v for v in set(e[0] for e in g) if firstPlayerWins(g,v)]
winningVertices([(1,2), (2,0), (0, 3), (3,2)])
## [0, 2, 3]
I am trying to make a Python program that churns out a solved sudoku puzzle. It randomly generates coordinates for a tile, and if that tile already has a number in it, it tries again. It then generates a number between 1 and 9 to put there, and if that number isn't already in that row, column, or section, it'll assign the value and add those coordinates to the list of occupied tiles. Once all the tiles are filled, it's supposed to exit the loop and return the completed grid.
The trouble is, it's always stopping for no reason after about 70 loops, causing the program to freeze.
Here is the code for the function I'm talking about:
def populate(grid):
usedCoords = []
populated = False
while not populated:
x = random.randrange(len(grid))
y = random.randrange(len(grid))
while [x,y] in usedCoords:
x = random.randrange(len(grid))
y = random.randrange(len(grid))
value = random.randrange(1, len(grid) + 1)
if not rowCheck(grid, x, y, value) and not columnCheck(grid, x, y, value) and not squareCheck(grid, x, y, value):
grid[x][y] = value
usedCoords.append([x,y])
print(len(usedCoords))
if len(usedCoords) == len(grid) ** 2:
populated = True
return grid
And here is the code for the functions it references:
def rowCheck(grid, x, y, value):
for i in range(len(grid)):
if not i == x:
if grid[i][y] == value:
return True
return False
def columnCheck(grid, x, y, value):
for i in range(len(grid)):
if not i==y:
if grid[x][i] == value:
return True
return False
def squareCheck(grid, x, y, value):
grid2 = [0] * (sectionSide) #new grid for the specific section
for i in range(len(grid2)):
grid2[i] = [0] * sectionSide
for i in range(x - (sectionSide - 1), x + sectionSide): #scanning only nearby coordinates
for j in range(y - (sectionSide - 1), y + sectionSide):
try:
if i // sectionSide == x // sectionSide and j // sectionSide == y // sectionSide:
grid2[i][j] = grid[x][y]
except IndexError:
pass
for i in range(len(grid2)):
for j in range(len(grid2[i])):
if grid2[i][j] == value and not (i == x and j == y):
return True
return False
There may be other issues, but a big problem with your code is that it has no way to backtrack if it finds it's created a board state that cannot be solved. Consider what would happen if your code put the following values on the first two rows of the board:
1 2 3 4 5 6 7 8 9
4 5 6 7 8 1 2 3
The numbers that have been placed so far are all legal, but there is no number that you can put in the last space of the second row. I'd guess that your code is eventually getting stuck when it creates a bunch of board positions like this, which cannot take any value. If there are no legal moves left it can make, it will keep on looping forever.
You need a more sophisticated algorithm to avoid this issue.
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):
I am representing a grid with a 2D list in python. I would like to pick a point (x,y) in the list and determine it's location...right edge, top left corner, somewhere in the middle...
Currently I am checking like so:
# left column, not a corner
if x == 0 and y != 0 and y != self.dim_y - 1:
pass
# right column, not a corner
elif x == self.dim_x - 1 and y != 0 and y != self.dim_y - 1:
pass
# top row, not a corner
elif y == 0 and x != 0 and x != self.dim_x - 1:
pass
# bottom row, not a corner
elif y == self.dim_y - 1 and x != 0 and x != self.dim_x - 1:
pass
# top left corner
elif x == 0 and y == 0:
pass
# top right corner
elif x == self.dim_x - 1 and y == 0:
pass
# bottom left corner
elif x == 0 and y == self.dim_y - 1:
pass
# bottom right corner
elif x == self.dim_x - 1 and y == self.dim_y - 1:
pass
# somewhere in middle; not an edge
else:
pass
Where I have some function do something after the location is determined
dim_x and dim_y are the dimensions of the list.
Is there a better way of doing this without so many if-else statements? Something efficient would be good since this part of the logic is being called a couple million times...it's for simulated annealing.
Thanks in advance. Also, what would be a better way of wording the title?
def location(x,y,dim_x,dim_y):
index = 1*(y==0) + 2*(y==dim_y-1) + 3*(x==0) + 6*(x==dim_x-1)
return ["interior","top","bottom","left","top-left",
"bottom-left","right","top-right","bottom-right"][index]
# initially:
method_list = [
bottom_left, bottom, bottom_right,
left, middle, right,
top_left, top, top_right,
]
# each time:
keyx = 0 if not x else (2 if x == self.dim_x - 1 else 1)
keyy = 0 if not y else (2 if y == self.dim_y - 1 else 1)
key = keyy * 3 + keyx
method_list[key](self, x, y, other_args)
Untested ... but the general idea should shine through.
Update after the goal posts were drastically relocated by "Something efficient would be good since this part of the logic is being called a couple million times...it's for simulated annealing":
Originally you didn't like the chain of tests, and said you were calling a function to handle each of the 8 cases. If you want fast (in Python): retain the chain of tests, and do the handling of each case inline instead of calling a function.
Can you use psyco? Also, consider using Cython.
If I understand correctly, you have a collection of coordinates (x,y) living in a grid, and you would like to know, given any coordinate, whether it is inside the grid or on an edge.
The approach I would take is to normalize the grid before making the comparison, so that its origin is (0,0) and its top right corner is (1,1), then I would only have to know the value of the coordinate to determine its location. Let me explain.
0) Let _max represent the maximum value and _min, for instance, x_min is the minimum value of the coordinate x; let _new represent the normalized value.
1) Given (x,y), compute: x_new = (x_max-x)/(x_max-x_min) and y_new=(y_max-y)/(y_max-y_min).
2) [this is pseudo code]
switch y_new:
case y_new==0: pos_y='bottom'
case y_new==1: pos_y='top'
otherwise: pos_y='%2.2f \% on y', 100*y_new
switch x_new:
case x_new==0: pos_x='left'
case x_new==1: pos_x='right'
otherwise: pos_x='%2.2f \% on x', 100*x_new
print pos_y, pos_x
It would print stuff like "bottom left" or "top right" or "32.58% on y 15.43% on x"
Hope that helps.
I guess if you really want to treat all these cases completely differently, your solution is okay, as it is very explicit. A compact solution might look more elegant, but will probably be harder to maintain. It really depends on what happens inside the if-blocks.
As soon as there is a common handling of, say, the corners, one might prefer to catch those cases with one clever if-statement.
Something like this might be more readable / maintainable. It will probably be a lot faster than your nested if statements since it only tests each condition once and dispatches through a dictionary which is nice and fast.
class LocationThing:
def __init__(self, x, y):
self.dim_x = x
self.dim_y = y
def interior(self):
print "interior"
def left(self):
print "left"
def right(self):
print "right"
def top(self):
print "top"
def bottom(self):
print "bottom"
def top_left(self):
print "top_left"
def top_right(self):
print "top_right"
def bottom_left(self):
print "bottom_left"
def bottom_right(self):
print "bottom_right"
location_map = {
# (left, right, top, bottom)
( False, False, False, False ) : interior,
( True, False, False, False ) : left,
( False, True, False, False ) : right,
( False, False, True, False ) : top,
( False, False, False, True ) : bottom,
( True, False, True, False ) : top_left,
( False, True, True, False ) : top_right,
( True, False, False, True ) : bottom_left,
( False, True, False, True ) : bottom_right,
}
def location(self, x,y):
method = self.location_map[(x==0, x==self.dim_x-1, y==0, y==self.dim_y-1)]
return method(self)
l = LocationThing(10,10)
l.location(0,0)
l.location(0,1)
l.location(1,1)
l.location(9,9)
l.location(9,1)
l.location(1,9)
l.location(0,9)
l.location(9,0)
When you run the above it prints
top_left
left
interior
bottom_right
right
bottom
bottom_left
top_right
For a fast inner-loop function, you can just bite the bullet and do the ugly: nested if else statements with repeated terms, so that each comparison is only done once, and it runs about twice as fast as an example cleaner answer (by mobrule):
import timeit
def f0(x, y, x_dim, y_dim):
if x!=0:
if x!=x_dim: # in the x interior
if y!=0:
if y!=y_dim: # y interior
return "interior"
else: # y==y_dim edge 'top'
return "interior-top"
else:
return "interior-bottom"
else: # x = x_dim, "right"
if y!=0:
if y!=y_dim: #
return "right-interior"
else: # y==y_dim edge 'top'
return "right-top"
else:
return "right-bottom"
else: # x=0 'left'
if y!=0:
if y!=y_dim: # y interior
return "left-interior"
else: # y==y_dim edge 'top'
return "left-top"
else:
return "left-bottom"
r_list = ["interior","top","bottom","left","top-left",
"bottom-left","right","top-right","bottom-right"]
def f1(x,y,dim_x,dim_y):
index = 1*(y==0) + 2*(y==dim_y-1) + 3*(x==0) + 6*(x==dim_x-1)
return r_list[index]
for x, y, x_dim, y_dim in [(4, 4, 5, 6), (0, 0, 5, 6)]:
t = timeit.Timer("f0(x, y, x_dim, y_dim)", "from __main__ import f0, f1, x, y, x_dim, y_dim, r_list")
print "f0", t.timeit(number=1000000)
t = timeit.Timer("f1(x, y, x_dim, y_dim)", "from __main__ import f0, f1, x, y, x_dim, y_dim, r_list")
print "f1", t.timeit(number=1000000)
Which gives:
f0 0.729887008667 # nested if-else for interior point (no "else"s)
f1 1.4765329361
f0 0.622623920441 # nested if-else for left-bottom (all "else"s)
f1 1.49259114265
So it's a bit better than twice as fast as mobrule's answer, which was the fastest looking code that I knew would work when I posted this. (Also, I moved mobrule's string list out of the function as that sped up the result by 50%.) Speed over beauty?
If instead you want a concise and easy to read solution, I suggest:
def f1(x, y, x_dim, y_dim):
d_x = {0:"left", x_dim:"right"}
d_y = {0:"bottom", y_dim:"top"}
return d_x.get(x, "interior")+"-"+d_y.get(y, "interior")
which is as fast as the others by my timing.