I have to write this function that generates a game tree for Othello (or Reversi), without using any library, that at the end gives me as an output a tuple (a,b,c) with:
a: the number of situations that result in a black win
b: the number of situations that result in a white win
c: the number of situations that result in a draw.
I have to work with these boards given as a txt file, as such:
. . W W
. . B B
W W W B
W B B W
The problem is I get a wrong output (different from the expected one). In the given exemple above, the output tuple should be (2, 16, 0), but I get (5, 7, 11). Below I will leave you the code, and I can't figure out what did I do wrong.
def generate_game_tree(filename: str):
# Load the board from the file
board = [line.split() for line in open(filename)]
# Initialize the game tree with the root node representing the current board state
game_tree = [(board, 0)]
black_count = 0
white_count = 0
draw_count = 0
# Generate the game tree by expanding the nodes in a breadth-first manner
i = 0
while i < len(game_tree):
node = game_tree[i]
board, _ = node
valid_moves = get_valid_moves(board)
for move in valid_moves:
new_board = make_move(board, move)
game_tree.append((new_board, i))
i += 1
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == "W":
white_count += 1
if board[i][j] == "B":
black_count += 1
else:
draw_count += 1
return black_count, white_count, draw_count
def make_move(board, move):
flips = []
x, y = move
curr_color = board[x][y]
for dx, dy in [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]:
x, y = move
x += dx
y += dy
if not (0 <= x < len(board) and 0 <= y < len(board[0])):
continue
if board[x][y] != '.':
continue
while board[x][y] != curr_color:
flips.append((x, y))
x += dx
y += dy
if not (0 <= x < len(board) and 0 <= y < len(board[0])):
break
return flips
def get_valid_moves(board):
valid_moves = []
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] != '.':
continue
flips = make_move(board, (i, j))
if flips:
valid_moves.append((i, j))
return valid_moves
Related
Basically, I want to find the number of unique paths on the maze, but however, I've tried this code and it's working for my first matrix of the binary maze, however, it only identifies 1 unique path, however, the answer should be 2 because, on the maze[3][0], it could take a new unique path using the "right-down" choice rather than going to maze[4][0] and taking "right" choice.
# Check if cell (x, y) is valid or not
def isValidCell(x, y, N, M):
return not (x < 0 or y < 0 or x >= N or y >= M)
def countPaths(maze, i, j, dest, visited):
# `N × M` matrix
N = len(maze)
M = len(maze[0])
# if destination (x, y) is found, return 1
if (i, j) == dest:
return 1
# stores number of unique paths from source to destination
count = 0
# mark the current cell as visited
visited[i][j] = True
# if the current cell is a valid and open cell
if isValidCell(i, j, N, M) and maze[i][j] == 1:
print(i)
# go down (i, j) ——> (i + 1, j)
if i + 1 < N and not visited[i + 1][j]:
print("down")
count += countPaths(maze, i + 1, j, dest, visited)
# go up (i, j) ——> (i - 1, j)
elif i - 1 >= 0 and not visited[i - 1][j]:
print("up")
count += countPaths(maze, i - 1, j, dest, visited)
# go right (i, j) ——> (i, j + 1)
elif j + 1 < M and not visited[i][j + 1]:
print("right")
count += countPaths(maze, i, j + 1, dest, visited)
# go right-down (diagonal) (i, j) ——> (i + 1, j + 1)
elif j + 1 < M and i + 1 < N and not visited[i + 1][j + 1]:
print("right down")
count += countPaths(maze, i + 1, j + 1, dest, visited)
# backtrack from the current cell and remove it from the current path
visited[i][j] = False
return count
def findCount(maze, src, dest):
# get source cell (i, j)
i, j = src
# get destination cell (x, y)
x, y = dest
# base case: invalid input
if not maze or not len(maze) or not maze[i][j] or not maze[x][y]:
return 0
# `N × M` matrix
N = len(maze)
M = len(maze[0])
print(M)
# 2D matrix to keep track of cells involved in the current path
visited = [[False for k in range(M)] for l in range(N)]
# start from source cell (i, j)
return countPaths(maze, i, j, dest, visited)
if name == 'main':
maze = [
[1, 0],
[1, 0],
[1, 0],
[1, 0],
[1, 1]
]
# source cell
src = (0, 0)
# destination cell
dest = (4, 1)
print("The total number of unique paths are", findCount(maze, src, dest))
The code assumes a square matrix
# `N × N` matrix
N = len(maze)
You are feeding it a rectangular matrix maze = 15x5 (didn't count the lines, guesstimated)
Let's say I have a two dimensional grid of 10x10 cells. The top left cell has coordinates (0,0) and
the bottom right cell has coordinates (9,9).
The code below doesn't seem to function the way I want it to.
I can't figure out what I am doing wrong.
'''
X = 10
Y = 10
class Cell:
def __init__(self,x,y) -> None:
self.coordinates = (x,y)
self.neigbors = self.find_neighbors()
def find_neighbors(self):
x,y = self.coordinates
neighbors = [
(x+1,y),(x-1,y),(x,y+1),(x,y-1),(x+1,y+1),
(x+1,y-1),(x-1,y+1),(x-1,y-1)
]
for neighbor in neighbors:
if neighbor[0] < 0 or neighbor[1] < 0:
neighbors.remove(neighbor)
elif neighbor[0] >= X or neighbor[1] >= Y:
neighbors.remove(neighbor)
return neighbors
cell1 = Cell(0,0)
cell1.neigbors
# [(1, 0), (0, 1), (1, 1), (-1, 1)]
# shouldn't have (-1,1)
cell2 = Cell(9,9)
cell2.neigbors
# [(8, 9), (9, 8), (10, 8), (8, 8)]
# shouldn't have (10,8)
'''
Better not to remove items from a list while iterating over it (as already pointed out in the comments). Here's an idea where you mark the entries in the list of potential coordinates as "unwanted" then subsequently reconstruct the desired output:
MX = 10
MY = 10
def neighbours(x, y):
pn = [(x-1, y), (x+1, y), (x-1, y-1), (x, y-1),
(x+1, y-1), (x-1, y+1), (x, y+1), (x+1, y+1)]
for i, t in enumerate(pn):
if t[0] < 0 or t[1] < 0 or t[0] >= MX or t[1] >= MY:
pn[i] = None
return [c for c in pn if c is not None]
print(neighbours(5, 4))
Instead of the for-loop put this piece.
neighbors = [neighbor for neighbor in neighbors if validate_cell(neighbor)]
The function validate_cell(coordinate).
validate_cell(coordinate):
if coordinate[0] < 0 or coordinate[1] < 0:
return False
elif coordinate[0] >= X or coordinate[1] >= Y:
return False
else:
return True
Given a maze, start coordinate, and end coordinate,
maze = [
[BLACK, BLACK, WHITE, WHITE],
[BLACK, WHITE, WHITE, WHITE],
[WHITE, WHITE, BLACK, WHITE],
[WHITE, WHITE, BLACK, WHITE],
]
s = Coordinate(3, 0)
e = Coordinate(0, 3)
I am trying to find the path from start to end using BFS.
Finding the path is straightforward, but I am struggling with keeping the path to the destination.
What I've tried is
directions = [(1, 0), (0, 1), (-1, 0), (0, -1)]
queue = collections.deque()
queue.append(s)
path = []
while queue:
curr = queue.popleft()
if curr == e:
path.append(curr)
return path
path.append(curr)
maze[curr.x][curr.y] = BLACK
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
continue
queue.append(Coordinate(new_x, new_y))
Something like this, but the result prints out all the nodes that I've visited, instead of the final path. Any tips on keeping the right path and removing the node that does not belong to the final path?
Instead of maintaining a path list, you can maintain an edge_to dictionary which keeps track of which previous vertex led to visiting a certain vertex. Whenever you add something to your queue, you can can update edge_to. A modified version of your function using this approach is as follows:
Coordinate = collections.namedtuple('Coordinate', ['x', 'y'])
def find_path(s, e):
directions = [(1, 0), (0, 1), (-1, 0), (0, -1)]
queue = collections.deque()
queue.append(s)
edge_to = {s: None}
while queue:
curr = queue.popleft()
if curr == e:
return path(edge_to, curr)
maze[curr.x][curr.y] = BLACK
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
continue
c = Coordinate(new_x, new_y)
edge_to[c] = curr
queue.append(c)
Notice the call to path(...) when you find your end vertex. That function just builds a list from the edge_to dictionary:
def path(edge_to, end):
curr = end
res = []
while curr != None:
res.append(curr)
curr = edge_to[curr]
return list(reversed(res))
For your given maze, and start and end coordinates, we get the following output:
s = Coordinate(3, 0)
e = Coordinate(0, 3)
print(find_path(s, e))
Output
[Coordinate(x=3, y=0), Coordinate(x=2, y=0), Coordinate(x=2, y=1), Coordinate(x=1, y=1), Coordinate(x=1, y=2), Coordinate(x=0, y=2), Coordinate(x=0, y=3)]
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
continue
queue.append(Coordinate(new_x, new_y))
your queue.append(Coordinate(new_x, new_y)) is running everytime your for loops iterates.
We want this condition to only happen when our if condition runs, so lets try something like this:
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
queue.append(Coordinate(new_x, new_y))
continue
When our if-condition is met, append it, and then continue. Let me know if this helps.
So, here is the code I am running and it's giving me a TypeError. I am trying to traverse a 2d array and then returning the path from starting point to the target point.
I applied Breadth-first search for the path traversing but seems like something is wrong with the algorithm.
class Grid:
def __init__(self, str1):
self.maze = str1.splitlines()
def get_start_cordinates(self):
rr = 0
cc = 0
return rr, cc
def main(self, r, c):
queue = []
visited = {}
visited[(r, c)] = (-1, -1)
queue.append((r, c))
while len(queue) > 0:
r, c = queue.pop(0)
if r == 4 and c == 2:
path_actual = []
while r != -1:
path_actual.append((r, c))
r, c = visited[(r, c)]
path_actual.reverse()
return path_actual
# avoid repetition of code: make a loop
for dx, dy in ((-1, 0), (0, -1), (1, 0), (0, 1), (1, 1), (1, -1), (-1, 1), (-1, -1)):
new_r = r + dy
new_c = c + dx
if (0 <= new_r < len(self.maze) and
0 <= new_c < len(self.maze[0]) and
not (new_r, new_c) in visited):
visited[(new_r, new_c)] = (r, c)
queue.append((new_r, new_c))
maze = Grid("""1 12 2 0 0
2 11 1 11 0
3 2 -1 9 0""")
path = Grid.main(*Grid.get_start_cordinates())
print(path)
This is the error I am getting:
path = Grid.main(*Grid.get_start_cordinates())
TypeError: get_start_cordinates() missing 1 required positional argument: 'self'
path = maze.main(*maze.get_start_cordinates())
use the object you created not the class.
I have attempted to make Conway's game of life in python, and then save the output into a picture, but I think there is something wrong with the logic as most of the pictures don't look quite correct. (see picture)
game of life pic:
import PIL.Image, random
WIDTH = 1366
HEIGHT = 768
ROUNDS = 10
DEAD = (0, 0, 0)
ALIVE = (0, 64, 255)
print("Creating image")
img = PIL.Image.new("RGB", (WIDTH, HEIGHT))
data = img.load()
print("Creating grid")
grid = []
for y in range(HEIGHT):
grid.append([])
for x in range(WIDTH):
grid[y].append(random.randint(0, 1))
for i in range(ROUNDS):
print("Starting round", i + 1, "of", ROUNDS)
for y in range(HEIGHT):
for x in range(WIDTH):
n = 0
for y2 in range(-1, 2):
for x2 in range(- 1, 2):
if x2 != 0 and y2 != 0 and grid[(y + y2) % HEIGHT][(x + x2) % WIDTH] == 1:
n += 1
if n < 2:
grid[y][x] = 0
elif n > 3:
grid[y][x] = 0
elif grid[y][x] == 1 and n > 1 and n < 4:
grid[y][x] = 1
elif grid[y][x] == 0 and n == 3:
grid[y][x] = 1
print("Rendering image")
for y in range(HEIGHT):
for x in range(WIDTH):
if grid[y][x] == 1:
data[x, y] = ALIVE
else:
data[x, y] = DEAD
print("Saving image")
img.save("gofl.png")
Your program cannot work correctly in its current state, because you compute the next generation in the same grid where the last generation is stored. You need a new (empty) grid to store the next generation. In your implementation you overwrite the last generation already while computing the next generation.