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]
Related
I'm trying to write a code that generates a visibility graph from a set of points and walls (obstacles). My algorithms is not correct and fails on some cases where there is more than one wall intersecting an edge between two points.
Here's kind of a pseudo-python code for my algorithm :
Intersect(wall, P, Q):
returns True if wall segment intersects with PQ segment
Cross(wall, P, Q):
returns True if wall segment crosses PQ segment
for i in range(len(nodes)):
for j in range(i + 1, len(nodes)):
flag = True
for wall in walls:
if (Cross(wall, nodes[i].pos, nodes[j].pos)):
flag = False
if (flag):
nodes[i].adj.append(nodes[j])
nodes[j].adj.append(nodes[i])
How can I fix my algorithm?
Here's one of the tests where it fails:
Walls :
w1 -> (1, 0),(2, 1)
w2 -> (2, 1),(3, 2)
Nodes to be checked:
node1 -> (0, 2)
node2 -> (4, 0)
There shouldn't be an edge but my algorithm generates an edge because the edge does not Cross any wall (it intersects but not cross).
For clarification, Cross means that two segments intersect (share a point,) but they don't share any point that is either the start or end of any of the two segments.
When the view ray just grazes a wall like this, you need to keep track of whether the grazing was at the left edge of the wall or at the right edge, as seen from viewpoint P.
def LeftIntersect(wall, P, Q):
if Cross(wall, P, Q):
return False
if not Intersect(wall, P, Q):
return False
if magnitude(cross_product(PQ, wall_midpoint)) <= 0:
return False
return True
def RightIntersect(wall, P, Q):
if Cross(wall, P, Q):
return False
if not Intersect(wall, P, Q):
return False
if magnitude(cross_product(PQ, wall_midpoint)) >= 0:
return False
return True
for i in range(len(nodes)):
for j in range(i + 1, len(nodes)):
crossCount = 0
leftIntersectCount = 0
rightIntersectCount = 0
for wall in walls:
if (Cross(wall, nodes[i].pos, nodes[j].pos)):
crossCount += 1
if (LeftIntersect(wall, nodes[i].pos, nodes[j].pos)):
leftIntersectCount += 1
if (RightIntersect(wall, nodes[i].pos, nodes[j].pos)):
rightIntersectCount += 1
visible = True
if (crossCount > 0)
visible = False
if (leftIntersect > 0 && rightIntersect > 0)
visible = False
if (visible):
nodes[i].adj.append(nodes[j])
nodes[j].adj.append(nodes[i])
The first way that comes to mind for me is to check every pair of three from [node_a, node_b, wall_start, wall_end] and see if the third point lines along the segment between the other two. A quick and accurate way to do this is first create a vector between each of the points, and take two dot products to make sure the "middle" point really does lie in the middle. Additionally is necessary to also check the direction of the vectors to make sure they are parallel, which is equally fast. If both pass, then the third point lies along the segment between the other two.
In python
def intersect(a, b, c):
(ax, ay), (bx, by), (cx, cy) = a, b, c
bax, bay = bx-ax, by-ay
bcx, bcy = bx-cx, by-cy
acx, acy = ax-cx, ay-cy
if bax*bcx + bay*bcy < 0: return False
if bax*acx + bay*acy > 0: return False
return bax*bcy == bay*bcx
In practice, it might be better to check bax*bcy == bay*bcx first, since it is just as fast but probably more likely to fail (and break early) for non-intersecting points.
To then check if any two points intersects a given wall-
def wall_hit(node1, node2, wall_start, wall_end):
return intersect(node1, node2, wall_start) or \
intersect(node1, node2, wall_end) or \
intersect(wall_start, wall_end, node1) or \
intersect(wall_start, wall_end, node2)
Since most of the checks will effectively "short-circuit" after the first or second check in each intersect() call, and each wall_hit() will short-circuit if any of them do hit, I don't think this would be too costly to implement.
If you need to optimize it, you could always compute + reuse the bax, bay = bx-ax, by-ay; ... calculations by either inlining all the function calls and reordering, or by computing them in a separate function and then caching with the lru_cache decorator from functools. Additionally, if you go with the inlining approach, you can likely reorder the conditionals and bax, bay = ... calculations to lazy evaluate them so that you may not need to compute all the intermediate values to assert hit/no_hit.
so I've got a list of questions as a dictionary, e.g
{"Question1": 3, "Question2": 5 ... }
That means the "Question1" has 3 points, the second one has 5, etc.
I'm trying to create all subset of question that have between a certain number of questions and points.
I've tried something like
questions = {"Q1":1, "Q2":2, "Q3": 1, "Q4" : 3, "Q5" : 1, "Q6" : 2}
u = 3 #
v = 5 # between u and v questions
x = 5 #
y = 10 #between x and y points
solution = []
n = 0
def main(n_):
global n
n = n_
global solution
solution = []
finalSolution = []
for x in questions.keys():
solution.append("_")
finalSolution.extend(Backtracking(0))
return finalSolution
def Backtracking(k):
finalSolution = []
for c in questions.keys():
solution[k] = c
print ("candidate: ", solution)
if not reject(k):
print ("not rejected: ", solution)
if accept(k):
finalSolution.append(list(solution))
else:
finalSolution.extend(Backtracking(k+1))
return finalSolution
def reject(k):
if solution[k] in solution: #if the question already exists
return True
if k > v: #too many questions
return True
points = 0
for x in solution:
if x in questions.keys():
points = points + questions[x]
if points > y: #too many points
return True
return False
def accept(k):
points = 0
for x in solution:
if x in questions.keys():
points = points + questions[x]
if points in range (x, y+1) and k in range (u, v+1):
return True
return False
print(main(len(questions.keys())))
but it's not trying all possibilities, only putting all the questions on the first index..
I have no idea what I'm doing wrong.
There are three problems with your code.
The first issue is that the first check in your reject function is always True. You can fix that in a variety of ways (you commented that you're now using solution.count(solution[k]) != 1).
The second issue is that your accept function uses the variable name x for what it intends to be two different things (a question from solution in the for loop and the global x that is the minimum number of points). That doesn't work, and you'll get a TypeError when trying to pass it to range. A simple fix is to rename the loop variable (I suggest q since it's a key into questions). Checking if a value is in a range is also a bit awkward. It's usually much nicer to use chained comparisons: if x <= points <= y and u <= k <= v
The third issue is that you're not backtracking at all. The backtracking step needs to reset the global solution list to the same state it had before Backtracking was called. You can do this at the end of the function, just before you return, using solution[k] = "_" (you commented that you've added this line, but I think you put it in the wrong place).
Anyway, here's a fixed version of your functions:
def Backtracking(k):
finalSolution = []
for c in questions.keys():
solution[k] = c
print ("candidate: ", solution)
if not reject(k):
print ("not rejected: ", solution)
if accept(k):
finalSolution.append(list(solution))
else:
finalSolution.extend(Backtracking(k+1))
solution[k] = "_" # backtracking step here!
return finalSolution
def reject(k):
if solution.count(solution[k]) != 1: # fix this condition
return True
if k > v:
return True
points = 0
for q in solution:
if q in questions:
points = points + questions[q]
if points > y: #too many points
return True
return False
def accept(k):
points = 0
for q in solution: # change this loop variable (also done above, for symmetry)
if q in questions:
points = points + questions[q]
if x <= points <= y and u <= k <= v: # chained comparisons are much nicer than range
return True
return False
There are still things that could probably be improved in there. I think having solution be a fixed-size global list with dummy values is especially unpythonic (a dynamically growing list that you pass as an argument would be much more natural). I'd also suggest using sum to add up the points rather than using an explicit loop of your own.
This is my pathfinding function:
def get_distance(x1,y1,x2,y2):
neighbors = [(-1,0),(1,0),(0,-1),(0,1)]
old_nodes = [(square_pos[x1,y1],0)]
new_nodes = []
for i in range(50):
for node in old_nodes:
if node[0].x == x2 and node[0].y == y2:
return node[1]
for neighbor in neighbors:
try:
square = square_pos[node[0].x+neighbor[0],node[0].y+neighbor[1]]
if square.lightcycle == None:
new_nodes.append((square,node[1]))
except KeyError:
pass
old_nodes = []
old_nodes = list(new_nodes)
new_nodes = []
nodes = []
return 50
The problem is that the AI takes to long to respond( response time <= 100ms)
This is just a python way of doing https://en.wikipedia.org/wiki/Pathfinding#Sample_algorithm
You should replace your algorithm with A*-search with the Manhattan distance as a heuristic.
One reasonably fast solution is to implement the Dijkstra algorithm (that I have already implemented in that question):
Build the original map. It's a masked array where the walker cannot walk on masked element:
%pylab inline
map_size = (20,20)
MAP = np.ma.masked_array(np.zeros(map_size), np.random.choice([0,1], size=map_size))
matshow(MAP)
Below is the Dijkstra algorithm:
def dijkstra(V):
mask = V.mask
visit_mask = mask.copy() # mask visited cells
m = numpy.ones_like(V) * numpy.inf
connectivity = [(i,j) for i in [-1, 0, 1] for j in [-1, 0, 1] if (not (i == j == 0))]
cc = unravel_index(V.argmin(), m.shape) # current_cell
m[cc] = 0
P = {} # dictionary of predecessors
#while (~visit_mask).sum() > 0:
for _ in range(V.size):
#print cc
neighbors = [tuple(e) for e in asarray(cc) - connectivity
if e[0] > 0 and e[1] > 0 and e[0] < V.shape[0] and e[1] < V.shape[1]]
neighbors = [ e for e in neighbors if not visit_mask[e] ]
tentative_distance = [(V[e]-V[cc])**2 for e in neighbors]
for i,e in enumerate(neighbors):
d = tentative_distance[i] + m[cc]
if d < m[e]:
m[e] = d
P[e] = cc
visit_mask[cc] = True
m_mask = ma.masked_array(m, visit_mask)
cc = unravel_index(m_mask.argmin(), m.shape)
return m, P
def shortestPath(start, end, P):
Path = []
step = end
while 1:
Path.append(step)
if step == start: break
if P.has_key(step):
step = P[step]
else:
break
Path.reverse()
return asarray(Path)
And the result:
start = (2,8)
stop = (17,19)
D, P = dijkstra(MAP)
path = shortestPath(start, stop, P)
imshow(MAP, interpolation='nearest')
plot(path[:,1], path[:,0], 'ro-', linewidth=2.5)
Below some timing statistics:
%timeit dijkstra(MAP)
#10 loops, best of 3: 32.6 ms per loop
The biggest issue with your code is that you don't do anything to avoid the same coordinates being visited multiple times. This means that the number of nodes you visit is guaranteed to grow exponentially, since it can keep going back and forth over the first few nodes many times.
The best way to avoid duplication is to maintain a set of the coordinates we've added to the queue (though if your node values are hashable, you might be able to add them directly to the set instead of coordinate tuples). Since we're doing a breadth-first search, we'll always reach a given coordinate by (one of) the shortest path(s), so we never need to worry about finding a better route later on.
Try something like this:
def get_distance(x1,y1,x2,y2):
neighbors = [(-1,0),(1,0),(0,-1),(0,1)]
nodes = [(square_pos[x1,y1],0)]
seen = set([(x1, y1)])
for node, path_length in nodes:
if path_length == 50:
break
if node.x == x2 and node.y == y2:
return path_length
for nx, ny in neighbors:
try:
square = square_pos[node.x + nx, node.y + ny]
if square.lightcycle == None and (square.x, square.y) not in seen:
nodes.append((square, path_length + 1))
seen.add((square.x, square.y))
except KeyError:
pass
return 50
I've also simplified the loop a bit. Rather than switching out the list after each depth, you can just use one loop and add to its end as you're iterating over the earlier values. I still abort if a path hasn't been found with fewer than 50 steps (using the distance stored in the 2-tuple, rather than the number of passes of the outer loop). A further improvement might be to use a collections.dequeue for the queue, since you could efficiently pop from one end while appending to the other end. It probably won't make a huge difference, but might avoid a little bit of memory usage.
I also avoided most of the indexing by one and zero in favor of unpacking into separate variable names in the for loops. I think this is much easier to read, and it avoids confusion since the two different kinds of 2-tuples had had different meanings (one is a node, distance tuple, the other is x, y).
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?
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
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
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.
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.
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_)