Related
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
I was trying to recreate Conway's Game of Life in python using Tkinter, but I got stuck while checking for the neighbors. I can't seem to find the solution to my problem.
If a cell is alive it is stored using numpy, 0 for the dead cells and 1 for the living cells. The function "get_neighbours" checks if the cell is not in a corner or next to a side and it returns the ammount of neighbours each cell has. The function "recalculate" basically makes a new board with 0s and 1s which then replaces the original board and redraws everything. But when compared to an already existing game the progress is different.
import tkinter as tk
import numpy as np
win = tk.Tk()
WIDTH = 500
HEIGHT = 500
vs = 10
absvs = vs
cells = np.zeros((WIDTH//vs, HEIGHT//vs), dtype=int)
cells_new = np.zeros((WIDTH//vs, HEIGHT//vs), dtype=int)
def get_neighbours(x, y):
total = 0
if x > 0:
total += cells[x - 1, y]
if x > 0 and y > 0:
total += cells[x - 1, y - 1]
if y > 0:
total += cells[x, y - 1]
if x > 0 and y < (HEIGHT // absvs - 1):
total += cells[x - 1, y + 1]
if x < (WIDTH // absvs - 1):
total += cells[x + 1, y]
if x < (WIDTH // absvs - 1) and y < (HEIGHT // absvs - 1):
total += cells[x + 1, y + 1]
if y < (HEIGHT // absvs - 1):
total += cells[x, y + 1]
if x > 0 and y < (HEIGHT // absvs - 1):
total += cells[x - 1, y + 1]
return total
def recalculate():
global cells, cells_new
for y in range(HEIGHT//absvs):
for x in range(WIDTH//absvs):
temp = get_neighbours(x, y)
if (temp == 2 and cells[x, y] == 1) or (temp == 3 and cells[x, y] == 1):
cells_new[x, y] = 1
elif temp == 3 and cells[x, y] == 0:
cells_new[x, y] = 1
elif temp < 2 or temp > 3:
cells_new[x, y] = 0
cells = cells_new
canvas.delete("all")
create_stage()
redraw_cell()
def slider_changer(e):
global vs
canvas.delete("all")
vs = w.get()
create_stage()
redraw_cell()
def create_cell(e):
global cells
tx = e.x // vs
ty = e.y // vs
x = tx * vs
y = ty * vs
canvas.create_rectangle(x, y, x + vs, y + vs, fill="gray")
cells[tx, ty] = 1
print(get_neighbours(tx, ty))
def redraw_cell():
for x in range(WIDTH//vs):
for y in range(HEIGHT//vs):
if cells[x, y] == 1:
canvas.create_rectangle(x * vs, y * vs, x * vs + vs, y * vs + vs, fill="gray")
def create_stage():
for x in range(WIDTH//vs):
canvas.create_line(x*vs, 0, x*vs, HEIGHT)
for y in range(HEIGHT//vs):
canvas.create_line(0, y*vs, WIDTH, y*vs)
canvas = tk.Canvas(width = WIDTH, height = HEIGHT, bg = "white")
canvas.pack()
w = tk.Scale(win, from_=10, to=50, orient="horizontal", command = slider_changer, length = 500)
w.pack()
w2 = tk.Button(win, text = "PRESS ME!!!", command = recalculate)
w2.pack()
create_stage()
canvas.bind("<Button-1>", create_cell)
win.mainloop()
There are these issues in your code:
In get_neighbours the last if block is the same as the fourth if block, and so there is a neighbor that is counted twice and another neighbor that isn't counted at all (the one at x + 1 and y - 1). So replace this:
if x > 0 and y < (HEIGHT // absvs - 1):
total += cells[x - 1, y + 1]
with:
if x < (WIDTH // absvs - 1) and y > 0:
total += cells[x + 1, y - 1]
In recalculate the assignment to cells at the end is making you lose one of the two arrays. Now both cells and new_cells reference the very same array1. This means the next iteration will not be calculated correctly. Instead, you should copy the content of cells_new into cells. So replace:
cells = cells_new
with:
cells = cells_new.copy()
In recalculate the if...elif..elif construct (inside the double loop) does not deal with the case where temp == 2 and cells[x, y] == 1 and so cells_new[x, y] might not be reset to 0 when it should. In fact, the last part of this construct should not have a condition; it should be a catch-all for all states that the previous checks did not deal with. So replace:
elif temp < 2 or temp > 3:
cells_new[x, y] = 0
with:
else:
cells_new[x, y] = 0
1 It is not helping that several websites, including https://www.geeksforgeeks.org/how-to-copy-numpy-array-into-another-array/ and https://www.askpython.com/python-modules/numpy/numpy-copy, wrongly assert that an assignment of one numpy array to another makes a copy. That is not true.
I have this program trying to add up the number of grid spaces around it of a certain value, and it keep giving the error "IndexError: list out of range". I've tried setting it to start on column and row late and end one column and row early to the same effect. The error points at [x+1][y+1] specifically.
for l in range(loops):
for x in range(self.width):
for y in range(self.height):
neighbors = 0
if tiles_copy[x-1][y-1] == 1:
neighbors += 1
if tiles_copy[x][y-1] == 1:
neighbors += 1
if tiles_copy[x+1][y-1] == 1:
neighbors += 1
if tiles_copy[x+1][y] == 1:
neighbors += 1
if tiles_copy[x+1][y+1] == 1:
neighbors += 1
if tiles_copy[x][y+1] == 1:
neighbors += 1
if tiles_copy[x-1][y+1] == 1:
neighbors += 1
if tiles_copy[x-1][y] == 1:
neighbors += 1
Not an answer
Can you change the start of your loop to
print(self.width, len(tiles_copy))
for x in range(1, self.width):
print(self.height, len(tiles_copy[x-1]), len(tiles_copy[x]), len(tiles_copy[x+1]))
for y in range(1, self.height):
len(tiles_copy) should be equal to self.width and the 3 values in the loop should be equal to self.height. At a guess, some of the values are less.
You have to prevent non existing indexes like -1 and self.width, self.width + 1 etc...
I think it's easier to make a function that does the check for the 8 point around each x y combination
# =========================================
def count_neighbors(self, x , y):
if self.width < 3 or self.height < 3:
return 0
neighbors = 0
x_range = []
y_range = []
# get only the valid x and y indexes ------------
if x > 0:
if x < self.width - 1:
x_range = range(x - 1, x + 1 + 1) # x-1, x, x+1
else:
x_range = range(x - 1, x + 1) # x-1, x
else:
# x == 0
x_range = range(x, x + 1 + 1) # x, x+1
if y > 0:
if y < self.width - 1:
y_range = range(y - 1, y + 1 + 1) # y-1, y, y+1
else:
y_range = range(y - 1, y + 1) # y-1, y
else:
# y == 0
y_range = range(y, y + 1 + 1) # y, y+1
for x_index in x_range:
for y_index in y_range:
if x_range == x and y_index == y:
# don't compare with itself
continue
if tiles_copy[x_index][y_index] == 1:
neighbors += 1
return neighbors
# ============================================
neighbors = 0
for l in range(loops):
for x in range(self.width):
for y in range(self.height):
neighbors += count_neighbors(x, y)
It's a bit tricky You have to test and debug this yourself.
I have an image that looks like this:
I've converted it into a 2D list. What's the best way to iterate through this list in a 'spiral', starting at the top left and ending in the centre. The goal is to read in all non-black pixels.
Here is code I have that progresses through spirals, starting in the upper left and going clockwise. It is naive (doesn't make use of that we know there are spirals) and simply prints out the coordinates, but I hope you can modify it to what you need.
I've checked several edge cases, because you need to make sure (0,1,2,3 mod 4) x (0,1,2,3 mod 4) all work. And wide spirals and squarish spirals need to be considered.
def do_stuff_with(my_array, x, y):
print("Do stuff with", x, ",", y) #obviously, you will want to return or manipulate something. But this code is just about traversing the spiral.
def spiral(my_array, width, height):
x_left = 0
x_right = width
y_top = 0
y_bottom = height
x_c = y_c = 0
print("Bounding box {0},{1} -> {2},{3}".format(x_left, y_top, x_right, y_bottom))
if x_left >= x_right or y_top >= y_bottom:
print("Invalid spiral range.")
return
while x_left < x_right and y_top < y_bottom:
#print("Going right")
for i in range(x_left, x_right):
do_stuff_with(my_array, i, y_top)
#print("Going down")
for i in range(y_top + 1, y_bottom):
do_stuff_with(my_array, x_right - 1, i)
if y_bottom - y_top > 1: # this just prevents us from revisiting a square in, say, a 5x7 spiral
#print("Going left")
for i in range(x_right - 2, x_left, -1):
do_stuff_with(my_array, i, y_bottom - 1)
if x_right - x_left > 1: # this just prevents us from revisiting a square in, say, a 7x5 spiral
#print("Going up")
for i in range(y_bottom - 1, y_top + 1, -1):
do_stuff_with(my_array, x_left, i)
# we need to fill in the square that links an outer spiral with an inner spiral.
if x_right - x_left > 2 and y_bottom - y_top > 4:
do_stuff_with(my_array, x_left + 1, y_top + 2)
x_left += 2
x_right -= 2
y_top += 2
y_bottom -= 2
print("Top/bottom overlap", y_top >= y_bottom)
print("Left/right overlap", x_left >= x_right)
def test_spirals(xi, yi, xf, yf):
'''an exhaustive test to make sure different rectangles/spirals work'''
for x in range(xi, xf):
for y in range(yi, yf):
print(x, y, "spiral test")
my_array = []
for z in range(0, y):
my_array.append([0] * x)
spiral(my_array, x, y)
# squarish tests: it seems like the main cases are (0/1/2/3 mod 4, 0/1/2/3 mod 4) so these 16 should knock everything out
test_spirals(4, 4, 8, 8)
# rectangular tests--yes, this overlaps the above case with 5x(6/7) but we want to try all possibilities mod 4 without having too much data to read.
#test_spirals(2, 6, 6, 10)
Let me know if you need or want clarifications.
ETA: here is some pseudocode for if you know you are reading in a spiral, but I think it is a big assumption. Also, this pseudocode is untested. But the idea is: go right til you hit a wall or black square, then down, then left, then up. Then repeat. Also, check for unnecessary trackbacks that may cause you to loop at the innermost line near the end.
def traverse_known_spiral(myary, width, length):
do_stuff(0, 0)
x_c = 0
y_c = 0
while True:
x_c_i = x_c
y_c_i = y_c
while x_c < width - 1 and myary[x_c+1][y_c] == WHITE:
do_stuff(x_c+1, y_c)
x_c += 1
while y_c < height - 1 and myary[x_c][y_c+1] == WHITE:
do_stuff(x_c, y_c+1)
y_c += 1
if x_c_i == x_c and y_c_i == y_c: break # if we did not move right or down, then there is nothing more to do
x_c_i = x_c
y_c_i = y_c
if y_c_i != y_c: # if we didn't go down, then we'd be going backwards here
while x_c > 0 and myary[x_c-1][y_c] == WHITE:
do_stuff(x_c-1, y_c)
x_c -= 1
if x_c_i != x_c: # if we didn't go right, then we'd be going backwards here
while y_c > 0 and myary[x_c-1][y_c-1] == WHITE:
do_stuff(x_c, y_c-1)
y_c -= 1
if x_c_i == x_c and y_c_i == y_c: break # if we did not move left or up, then there is nothing more to do
So I created a recurring pattern using two different designs. The problem now is making the colours, so I have to ask the user to pick 3 different colours and use them to make certain areas of the designs that colour. For example what I mean is, if I were to choose the 5x5 patch layout then the first 2 boxes of patterns across and below should be one colour (so that's four boxes) and so on.
EDIT: to clear up the confusion.
So if you look at the 500x500 one. type '5' when it asks for what patch size you want.
you should see two different pattern designs.
all I want is the x = 0 to x = 200 and y = 0 to y = 200 to be blue colour. Meaning that everything in that area (by area, I mean all the pattern designs that are in there) should be a blue colour. The same colour should be used for x = 300 to x = 500 and y = 300 to y = 500.
the x = 300 to x = 500 and y = 0 to y = 200 should be red colour and the x = 0 to x = 200 and y = 300 to y = 500 should also be red.
and all that is left is the '+' part in the middle which should have the third colour, green.
https://prntscr.com/m48xxd this is what I mean by the colours. so the top left and bottom right are the same colours. the top right and bottom left are te same colours and the '+' is another colour.
7.https://prntscr.com/m48xzm here is the 7x7 version
I just put colours, for now, to make it easier to look at.
I have already tried doing the 'getX' and 'getY' but I can't seem to get it right.
from graphics import *
def main():
global patchWorkSize
patchWorkSize = int(input("What patchwork size would you like?"))
if patchWorkSize == 5:
patchWork5()
elif patchWorkSize == 7:
patchWork7()
elif patchWorkSize == 9:
patchWork9()
else:
print('Only patchwork sizes 5, 7 and 9 are avaliable')
def patchWork5():
layout()
for y in range(0,500,100):
for x in range(0,500,200):
for l1 in range(0,100,20):
line = Line(Point(l1+x,0+y), Point(100+x,100-l1+y))
line.draw(win)
for l2 in range(100,0,-20):
line3 = Line(Point(l2+x,100+y), Point(0+x,100-l2+y))
line3.draw(win)
for l3 in range(100,0,-20):
line2 = Line(Point(100+x,0+l3+y), Point(0+l3+x,100+y))
line2.setFill('red')
line2.draw(win)
for l4 in range(0,100,20):
line4 = Line(Point(0+x,100-l4+y), Point(100-l4+x,0+y))
line4.setFill('red')
line4.draw(win)
for e in range(0,500,100):
for t in range(100,500,200):
for o in range(0,500,10):
for c in range(5,100,10):
circle1 = Circle(Point(c+t,5+o+e), 5)
circle1.setFill('blue')
circle1.draw(win)
def patchWork7():
layout()
for y in range(0,700,100):
for x in range(0,700,200):
for l1 in range(0,100,20):
line = Line(Point(l1+x,0+y), Point(100+x,100-l1+y))
line.draw(win)
for l2 in range(100,0,-20):
line3 = Line(Point(l2+x,100+y), Point(0+x,100-l2+y))
line3.draw(win)
for l3 in range(100,0,-20):
line2 = Line(Point(100+x,0+l3+y), Point(0+l3+x,100+y))
line2.setFill('red')
line2.draw(win)
for l4 in range(0,100,20):
line4 = Line(Point(0+x,100-l4+y), Point(100-l4+x,0+y))
line4.setFill('red')
line4.draw(win)
for e in range(0,700,100):
for t in range(100,700,200):
for o in range(0,700,10):
for c in range(5,100,10):
circle1 = Circle(Point(c+t,5+o+e), 5)
circle1.setFill('blue')
circle1.draw(win)
def patchWork9():
layout()
for y in range(0,900,100):
for x in range(0,900,200):
for l1 in range(0,100,20):
line = Line(Point(l1+x,0+y), Point(100+x,100-l1+y))
line.draw(win)
for l2 in range(100,0,-20):
line3 = Line(Point(l2+x,100+y), Point(0+x,100-l2+y))
line3.draw(win)
for l3 in range(100,0,-20):
line2 = Line(Point(100+x,0+l3+y), Point(0+l3+x,100+y))
line2.setFill('red')
line2.draw(win)
for l4 in range(0,100,20):
line4 = Line(Point(0+x,100-l4+y), Point(100-l4+x,0+y))
line4.setFill('red')
line4.draw(win)
for e in range(0,900,100):
for t in range(100,900,200):
for o in range(0,900,10):
for c in range(5,100,10):
circle1 = Circle(Point(c+t,5+o+e), 5)
circle1.setFill('blue')
circle1.draw(win)
def layout():
global win
win = GraphWin('Patchwork',patchWorkSize*100, patchWorkSize*100)
for i in range(0, patchWorkSize*100,100):
line = Line(Point(i,0), Point(i, patchWorkSize*100))
line2 = Line(Point(0,i), Point(patchWorkSize*100, i))
line.draw(win)
line2.draw(win)
main()
The key is to paramertize everything into cubes of a convenient size, and then turn the filling code into subroutines that can be called to fill those cubes. Below is a rework of your code along these lines that can handle any odd number 3 or greater as an input:
from graphics import *
UNIT = 100
def patchWork(win, size):
blocks = size // 2
def hatch_box(x, y):
for n in range(0, UNIT, UNIT//5):
line = Line(Point(n + x * UNIT, y * UNIT), Point((x + 1) * UNIT, UNIT - n + y * UNIT))
line.draw(win)
for n in range(UNIT, 0, -UNIT//5):
line = Line(Point(n + x * UNIT, (y + 1) * UNIT), Point(x * UNIT, UNIT - n + y * UNIT))
line.draw(win)
for n in range(UNIT, 0, -UNIT//5):
line = Line(Point((x + 1) * UNIT, n + y * UNIT), Point(n + x * UNIT, (y + 1) * UNIT))
line.setFill('red')
line.draw(win)
for n in range(0, UNIT, UNIT//5):
line = Line(Point(x * UNIT, UNIT - n + y * UNIT), Point(UNIT - n + x * UNIT, y * UNIT))
line.setFill('red')
line.draw(win)
for y in range(blocks):
for x in range(blocks):
hatch_box(x, y)
for y in range(blocks + 1, 2 * blocks + 1):
for x in range(blocks + 1, 2 * blocks + 1):
hatch_box(x, y)
def draw_circles(x, y):
for o in range(0, UNIT, UNIT // 10):
for c in range(0, UNIT, UNIT // 10):
circle = Circle(Point(c + UNIT // 20 + x * UNIT, o + UNIT // 20 + y * UNIT), UNIT // 20)
circle.setFill('blue')
circle.draw(win)
for y in range(blocks):
for x in range(blocks + 1, 2 * blocks + 1):
draw_circles(x, y)
draw_circles(y, x)
def draw_cube(x, y):
cube = Rectangle(Point(x * UNIT, y * UNIT), Point((x + 1) * UNIT, (y + 1) * UNIT))
cube.setFill('yellow')
cube.draw(win)
x = blocks
for y in range(0, 2 * blocks + 1):
draw_cube(x, y)
draw_cube(y, x)
def layout(size):
win = GraphWin('Patchwork', size * UNIT, size * UNIT)
for i in range(0, size * UNIT, UNIT):
Line(Point(i, 0), Point(i, size * UNIT)).draw(win)
Line(Point(0, i), Point(size * UNIT, i)).draw(win)
return win
def main():
patchWorkSize = int(input("What patchwork size would you like? "))
if patchWorkSize % 2 == 1 and patchWorkSize > 2:
win = layout(patchWorkSize)
patchWork(win, patchWorkSize)
win.getMouse() # pause for click in window
win.close()
else:
print('Only odd sizes 3 or greater are available')
main()
You should be able to change UNIT, within reason.
I'm not going to write everything for you, but the code below shows how to choose the color for each area based on its position within the patchwork grid (i and j). The color is determined in color_func().
Hopefully this will be enough for you to figure out how to apply the relatively simple coding pattern shown to draw the desired graphics.
Hint: Compute the coordinates of each graphics element based on (or relative to) these same position values (i.e. don't write a separate function for every possible patchwork size).
This code still seems overly repetitive to me, and could probably be made more concise, but I'll leave that to you, too... ;¬)
def main():
# size = int(input("What patchwork size would you like?"))
# Hardcoded for testing.
#
# if size % 2 == 0:
# raise RuntimeError('patchwork size must be odd')
patch_work(5)
print()
patch_work(7)
def patch_work(n):
colors = '0', '1', '2'
mid = n // 2
# Call color_func() for every possible position in patchwork and
# prints the results in rows.
for i in range(n):
row = []
for j in range(n):
row.append(color_func(n, i, j, colors))
print(''.join(row))
def color_func(n, i, j, colors):
mid = n // 2
if i == mid:
index = 2
elif i < mid:
if j < mid:
index = 0
elif j == mid:
index = 2
elif j > mid:
index = 1
elif i > mid:
if j < mid:
index = 1
elif j == mid:
index = 2
elif j > mid:
index = 0
return colors[index]
if __name__ == '__main__':
main()
Output:
00211
00211
22222
11200
11200
0002111
0002111
0002111
2222222
1112000
1112000
1112000