Backtracking and recursion in the n-queens problem (Python) - python

I am writing a python class to find a solution to the 8 queens problem. How can I implement backtracking properly in my solve method? I think the recursion should work, however, the program stops after the solution is not found on the first try, and backtracking does not take place. All helper methods work properly.
EMPTY = 0
QUEEN = 1
RESTRICTED = 2
class Board:
# initializes a 8x8 array
def __init__ (self):
self.board = [[EMPTY for x in range(8)] for y in range(8)]
# pretty prints board
def printBoard(self):
for row in self.board:
print(row)
# places a queen on a board
def placeQueen(self, x, y):
# restricts row
self.board[y] = [RESTRICTED for i in range(8)]
# restricts column
for row in self.board:
row[x] = RESTRICTED
# places queen
self.board[y][x] = QUEEN
self.fillDiagonal(x, y, 0, 0, -1, -1) # restricts top left diagonal
self.fillDiagonal(x, y, 7, 0, 1, -1) # restructs top right diagonal
self.fillDiagonal(x, y, 0, 7, -1, 1) # restricts bottom left diagonal
self.fillDiagonal(x, y, 7, 7, 1, 1) # restricts bottom right diagonal
# restricts a diagonal in a specified direction
def fillDiagonal(self, x, y, xlim, ylim, xadd, yadd):
if x != xlim and y != ylim:
self.board[y + yadd][x + xadd] = RESTRICTED
self.fillDiagonal(x + xadd, y + yadd, xlim, ylim, xadd, yadd)
# recursively places queens such that no queen shares a row or
# column with another queen, or in other words, no queen sits on a
# restricted square. Should solve by backtracking until solution is found.
def solve(self, queens):
if queens == 8:
return True
for i in range(8):
if self.board[i][queens] == EMPTY:
self.placeQueen(queens, i)
if self.solve(queens - 1):
return True
self.board[i][queens] = RESTRICTED
return False
b1 = Board()
b1.solve(7)
b1.printBoard()
Is my problem in the lack of a deep copy of the board before adding the queen, or is it just a lack of backtracking?

It's both: you have only one copy of the board in your entire program. You fill it as best you can, until all squares are occupied or restricted; the search fails, and you return from solve. With no mechanism to reset the board, your program ends.
Backtracking would make this simple, at the cost of multiple intermediate boards. Instead of having a single board object ... make a deep copy, place the queen, mark the appropriate RESTRICTED squares, and pass that altered copy to the next level. If you return with failure, let that copy evaporate naturally, being a local variable.

Related

Cellular Automata using python class

I have made a class which initiates and updates the CA data, and I have made a function 'Simulate' which updates the cells based on the rule that fire spreads across trees, and leaves empty spaces. Empty spaces are replaced with trees based on a given probability.
There is a problem where it appears my function is applying the rule to the current time data holder, rather than the previous time data holder. I have set prevstate = self.state to act as a temporary data holder for the previous iteration, but running small tests I find that it gives the same results as if I didn't include this line at all. What am I doing wrong?
import numpy as np
import random
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, colorConverter
from matplotlib.animation import FuncAnimation
#dimentions:
x = 10
y = 10
lighting = 0 #set to 0 for testing
grow = 0.3
#parameter values
empty = 0
tree = 1
fire = 2
random.seed(1)
#CA Rule definition
def update(mat, i, j, lighting, grow, prob):
if mat[i, j] == empty:
if prob < grow:
return tree
else:
return empty
elif mat[i, j] == tree:
if max(mat[i-1, j], mat[i+1, j], mat[i, j-1], mat[i, j+1]) == fire:
return fire
elif prob < lighting:
return fire
else:
return tree
else:
return empty
########## Data Holder
class Simulation:
def __init__(self):
self.frame = 0
#self.state = np.random.randint(2, size=(x, y)) commented out for testing
self.state = np.ones((x, y))
self.state[5, 5] = 2 #initial fire started at this location for testing
def updateS(self):
prevstate = self.state #line of code i think should be passing previous iteration through rule
for i in range(1, y-1):
for j in range(1, x-1):
prob = random.random()
self.state[i, j] = update(prevstate, i, j, lighting, grow, prob)
def step(self):
self.updateS()
self.frame += 1
simulation = Simulation()
figure = plt.figure()
ca_plot = plt.imshow(simulation.state, cmap='seismic', interpolation='bilinear', vmin=empty, vmax=fire)
plt.colorbar(ca_plot)
transparent = colorConverter.to_rgba('black', alpha=0)
#wall_colormap = LinearSegmentedColormap.from_list('my_colormap', [transparent, 'green'], 2)
def animation_func(i):
simulation.step()
ca_plot.set_data(simulation.state)
return ca_plot
animation = FuncAnimation(figure, animation_func, interval=1000)
mng = plt.get_current_fig_manager()
mng.window.showMaximized()
plt.show()
Any comments on better ways to implement a CA are most welcome!
Python assignments are pointers... So when you update self.state, then prevstate is also updated.
I expect if you set to:
prevstate = copy.copy(self.state)
That should fix your problem.
Copy docs
As jabberwocky correctly notes, your problem is that the line prevstate = self.state makes prevstate a new reference to the same numpy array as self.state, so that modifying the contents of one also modifies the other.
Instead of copying the array on every iteration, however, a slightly more efficient solution would be to preallocate two arrays and swap them, something like this:
class Simulation:
def __init__(self):
self.frame = 0
self.state = np.ones((x, y))
self.state[5, 5] = 2
self.prevstate = np.ones((x, y)) # <-- add this line
def updateS(self):
self.state, self.prevstate = self.prevstate, self.state # <-- swap the buffers
for i in range(1, y-1):
for j in range(1, x-1):
prob = random.random()
self.state[i, j] = update(self.prevstate, i, j, lighting, grow, prob)
I say "slightly" because all you're really saving is a numpy array copy and some work for the garbage collector. However, if you optimize your inner state update loop enough — maybe e.g. implementing the CA rule using numba — the relative cost of an extra array copy will start to be significant. In any case, there are no real down sides to using this "double buffering" method, so it's a good habit to pick up.

How can i append multiple values to numPy array?

I am making a snake game in pygame, and i need to make an array of pygame rects. When i was testing the code to see if the basic idea works, it didn't. When it was supposed to print
[[0,0],
[10,0],
[20,0],
and so on until it got to the biggest x value, and then it would add ten to the y value, it just prints the x values when the y value is always 0. I am new to pygame and python, so any help would be appreciated.
My code:
class Grid:
def __init__(self, gridSize):
self.gridSize = gridSize
self.numX = int(screenX / gridSize)
self.numY = int(screenX / gridSize)
self.xList = []
for y in range(0, self.numY * 10, 10):
for x in range(0, self.numX * 10, 10):
self.xList.append((x,y))
if y == 0:
self.array = np.array(self.xList)
else:
np.append(self.array, self.xList)
self.xList = []
print(self.array)
Most (if not all) numpy commands don't modify their arrays in-place. They return a new array, but the old array stays as it is.
Thus, under your else, you'll need
else:
self.array = np.append(self.array, self.xList)
This will update self.array so that it holds the new, appended array.
It also explains why you're only seeing print-outs for y = 0 and not other values. (You could possibly arrive at this same conclusion by debugging and stepping through your code. Maybe next time? :-) )
For starters, you aren't iterating over the Y range:
self.numY = int(screenX / gridSize) needs to be self.numY = int(screenY/ gridSize)

Ensure that contents of list sums up to 1 for np.random.choice()

The Context
In Python 3.5, I'm making a function to generate a map with different biomes - a 2-dimensional list with the first layer representing the lines of the Y-axis and the items representing items along the X-axis.
Example:
[
["A1", "B1", "C1"],
["A2", "B2", "C2"],
["A3", "B3", "C3"]
]
This displays as:
A1 B1 C1
A2 B2 C2
A3 B3 C3
The Goal
A given position on the map should be more likely to be a certain biome if its neighbours are also that biome. So, if a given square's neighbours are all Woods, that square is almost guaranteed to be a Woods.
My Code (so far)
All the biomes are represented by classes (woodsBiome, desertBiome, fieldBiome). They all inherit from baseBiome, which is used on its own to fill up a grid.
My code is in the form of a function. It takes the maximum X and Y coordinates as parameters. Here it is:
def generateMap(xMax, yMax):
areaMap = [] # this will be the final result of a 2d list
# first, fill the map with nothing to establish a blank grid
xSampleData = [] # this will be cloned on the X axis for every Y-line
for i in range(0, xMax):
biomeInstance = baseBiome()
xSampleData.append(biomeInstance) # fill it with baseBiome for now, we will generate biomes later
for i in range(0, yMax):
areaMap.append(xSampleData)
# now we generate biomes
yCounter = yMax # because of the way the larger program works. keeps track of the y-coordinate we're on
for yi in areaMap: # this increments for every Y-line
xCounter = 0 # we use this to keep track of the x coordinate we're on
for xi in yi: # for every x position in the Y-line
biomeList = [woodsBiome(), desertBiome(), fieldBiome()]
biomeProbabilities = [0.0, 0.0, 0.0]
# biggest bodge I have ever written
if areaMap[yi-1][xi-1].isinstance(woodsBiome):
biomeProbabilities[0] += 0.2
if areaMap[yi+1][xi+1].isinstance(woodsBiome):
biomeProbabilities[0] += 0.2
if areaMap[yi-1][xi+1].isinstance(woodsBiome):
biomeProbabilities[0] += 0.2
if areaMap[yi+1][xi-1].isinstance(woodsBiome):
biomeProbabilities[0] += 0.2
if areaMap[yi-1][xi-1].isinstance(desertBiome):
biomeProbabilities[1] += 0.2
if areaMap[yi+1][xi+1].isinstance(desertBiome):
biomeProbabilities[1] += 0.2
if areaMap[yi-1][xi+1].isinstance(desertBiome):
biomeProbabilities[1] += 0.2
if areaMap[yi+1][xi-1].isinstance(desertBiome):
biomeProbabilities[1] += 0.2
if areaMap[yi-1][xi-1].isinstance(fieldBiome):
biomeProbabilities[2] += 0.2
if areaMap[yi+1][xi+1].isinstance(fieldBiome):
biomeProbabilities[2] += 0.2
if areaMap[yi-1][xi+1].isinstance(fieldBiome):
biomeProbabilities[2] += 0.2
if areaMap[yi+1][xi-1].isinstance(fieldBiome):
biomeProbabilities[2] += 0.2
choice = numpy.random.choice(biomeList, 4, p=biomeProbabilities)
areaMap[yi][xi] = choice
return areaMap
Explanation:
As you can see, I'm starting off with an empty list. I add baseBiome to it as a placeholder (up to xi == xMax and yi == 0) in order to generate a 2D grid that I can then cycle through.
I create a list biomeProbabilities with different indices representing different biomes. While cycling through the positions in the map, I check the neighbours of the chosen position and adjust a value in biomeProbabilities based on its biome.
Finally, I use numpy.random.choice() with biomeList and biomeProbabilities to make a choice from biomeList using the given probabilities for each item.
My Question
How can I make sure that the sum of every item in biomeProbabilities is equal to 1 (so that numpy.random.choice will allow a random probability choice)? There are two logical solutions I see:
a) Assign new probabilities so that the highest-ranking biome is given 0.8, then the second 0.4 and the third 0.2
b) Add or subtract equal amounts to each one until the sum == 1
Which option (if any) would be better, and how would I implement it?
Also, is there a better way to get the result without resorting to the endless if statements I've used here?
This sounds like a complex way to approach the problem. It will be difficult for you to make it work this way, because you are constraining yourself to a single forward pass.
One way you can do this is choose a random location to start a biome, and "expand" it to neighboring patches with some high probability (like 0.9).
(note that there is a code error in your example, line 10 -- you have to copy the inner list)
import random
import sys
W = 78
H = 40
BIOMES = [
('#', 0.5, 5),
('.', 0.5, 5),
]
area_map = []
# Make empty map
inner_list = []
for i in range(W):
inner_list.append(' ')
for i in range(H):
area_map.append(list(inner_list))
def neighbors(x, y):
if x > 0:
yield x - 1, y
if y > 0:
yield x, y - 1
if y < H - 1:
yield x, y + 1
if x < W - 1:
yield x + 1, y
for biome, proba, locations in BIOMES:
for _ in range(locations):
# Random starting location
x = int(random.uniform(0, W))
y = int(random.uniform(0, H))
# Remember the locations to be handled next
open_locations = [(x, y)]
while open_locations:
x, y = open_locations.pop(0)
# Probability to stop
if random.random() >= proba:
continue
# Propagate to neighbors, adding them to the list to be handled next
for x, y in neighbors(x, y):
if area_map[y][x] == biome:
continue
area_map[y][x] = biome
open_locations.append((x, y))
for y in range(H):
for x in range(W):
sys.stdout.write(area_map[y][x])
sys.stdout.write('\n')
Of course a better method, the one usually used for those kinds of tasks (such as in Minecraft), is to use a Perlin noise function. If the value for a specific area is above some threshold, use the other biome. The advantages are:
Lazy generation: you don't need to generate the whole area map in advance, you determine what type of biome is in an area when you actually need to know that area
Looks much more realistic
Perlin gives you real values as output, so you can use it for more things, like terrain height, or to blend multiple biomes (or you can use it for "wetness", have 0-20% be desert, 20-60% be grass, 60-80% be swamp, 80-100% be water)
You can overlay multiple "sizes" of noise to give you details in each biome for instance, by simply multiplying them
I'd propose:
biomeProbabilities = biomeProbabilities / biomeProbabilities.sum()
For your endless if statements I'd propose to use a preallocated array of directions, like:
directions = [(-1, -1), (0, -1), (1, -1),
(-1, 0), (1, 0),
(-1, 1), (0, 1), (1, 1)]
and use it to iterate, like:
for tile_x, tile_y in tiles:
for x, y in direction:
neighbor = map[tile_x + x][tile_y + y]
#remram did a nice answer about the algorithm you may or may not use to generate terrain, so I won't go to this subject.

Printing small rectangles to the screen in a for loop. (pygame)

I'm trying to get a code to print small rectangles all over my screen in pygame with the help of for loops, but having trouble. I have solved parts of it with this code but it looks ugly and preforms bad:
x = 0
y = 0
for y_row in range(60):
y = y + 10
pygame.draw.rect(screen, GREEN, [x, y, 5, 5], 0)
for x_row in range(70):
pygame.draw.rect(screen, GREEN, [x, y, 5, 5], 0)
x = x + 10
x = 0
To start of, I do not believe I have to assign a value to x and y if I just can figure out how to implement the value of y_row and x_row at x and y's places instead, now it increases with 1, it should increase with 10, than I can implement it instead.
Another problem with the code is that it leaves a blank row at the top, this is because I had to add the y = y + 10 above the pygame draw, otherwise it just printed one rectangle there witch made it more visible.
The template I'm using to get the code working you can find Here.
Drawing 4,200 rectangles to the screen every 60th of a second is probably a significant task for the CPU. I suspect that the pygame.draw.rect() function is fairly high-level and calls are not batched by pygame making it sub-optimal, there is a hint in the documentation (https://www.pygame.org/docs/ref/draw.html#pygame.draw.rect) that Surface.fill(color, rect=None, special_flags=0) can be hardware accelerated and may be a faster option if you're filling the rectangles.
Note: the code examples below are pseudo ... just means you need to fill in the gaps.
You only need 1 call to pygame.draw.rect per iteration of the loop not 2 as you have now, e.g.
for row in rows:
y = ...
for col in cols:
x = ...
... draw rect ...
One easy win for performance is to not draw anything that's off-screen, so test your x, y coordinates before rendering, e.g:
screen_width = 800
screen_height = 600
for ...
y = y += 10
if y > screen_height:
break
for ...
x += 10
if x > screen_width:
break
... draw block ...
The same approach could also be used (with a continue) to implement an offset (e.g a starting offset_x, offset_y value) where rectangles with negative x, y values are not rendered (the test is not x < 0 however, but x < -block_size).
There's nothing wrong with calculating the x and y values from a loop index as you are doing, it's often useful to have an index (for example the index [row][col] might give you the location of data for a tile in a 2D matrix representing game tiles). I would calculate the x, y values myself from the indexes using a multiplier (this also solves the blank first row issue):
block_size = 10
for row in ...
y = row * block_size
if y > screen_height:
break
for col in ...
x = col * block_size
if x > screen_width:
break
... draw block ...
If you're using Python2 then you might consider using xrange to predefine the loop ranges to improve performance (though I imagine only a small amount and as always with optimization testing the performance difference is key). For example:
rows = xrange(60)
cols = xrange(70)
for row in rows:
...
for cols in cols:
... draw block ...
As #bshuster13 mentioned you can use pythons range() function and pass an optional step and stop argument to create a list containing arithmetic progressions.
numberOfRows = 60
numberOfColumns = 70
stepBetweenRects = 10
for y in range(0, numberOfRows * stepBetweenRects, stepBetweenRects):
for x in range(0, numberOfColumns * stepBetweenRects, stepBetweenRects):
pygame.draw.rect(screen, GREEN, (x, y, 5, 5), 0)

Random Walk Problem(Escape Recursion)

As practice, and as a precursor to a more complex, larger project I have in mind, I have created a random walk script using the Turtle module. I realize that there are simpler ways to do the random walk without having to find the neighboring coordinates, but as far as I can tell this is necessary for the larger implementation.
The problem I am having is that python is reaching its maximum recursion depth when it finds that it has visited every adjacent cell in the getnext() function. I'm not sure how I would escape that loop and continue on as normal should that occur.
import turtle
import random
class cell(object):
def __init__(self, pos, visited = False):
self.xCoord = pos[0]
self.yCoord = pos[1]
self.visited = visited
self.neigh = []
self.neighbors = self.getneighbors()
def getneighbors(self):
for j in (-1, 0, 1):
for i in (-1, 0, 1):
self.neigh.append((self.xCoord+i, self.yCoord+j))
def getnext():
nextindex = random.randint(0, len(c.neigh)-1)
nextcoordt = c.neigh[nextindex]
nextcoord = list(c.neigh[nextindex])
if nextcoordt in coords:
getnext()
else:
turtle.goto(nextcoord[0], nextcoord[1])
coords = {}
turtle.setup(width =200, height = 200, startx = 0, starty = 0)
turtle.trace = False
for i in range(1000):
c = cell(list(turtle.pos()))
coords[turtle.pos()] = (c)
getnext()
Furthermore this is actually my first true application of OOP and I was wondering if this was a good way to use it.
Thanks a lot!
If your random walk finds that it has visited every adjacent cell, it would loop forever. Since you're using recursion it quickly exceeds the maximum recursion limit.
I'm sure this could be written in an OOP way, but the problems are more in your use of recursion than whether the cell class is useful. For example, I've simplified your code to run in a linear fashion. The changes are:
Eliminate the (0, 0) direction since it makes no forward progress. (optional depending on your goal, i.e. if you consider "staying put" a valid move or not).
Uses random.choice() to pick the direction of the next move.
Removes recursion in favor of calculating the next coordinate by adding the direction vector to the current position. A simple loop suffices.
Doesn't check the next position against a recent history of positions, since a move back to a previous space is perfectly valid for randomness.
Code:
import itertools
import random
import turtle
# change step size if you like
STEP = 1
PTS = [-STEP, 0, STEP]
DIRS = [(x, y) for x in PTS for y in PTS if x or y]
turtle.setup(width=400, height=400, startx=0, starty=0)
turtle.trace = False
pos = turtle.pos()
for i in range(1000):
px, py = turtle.pos()
# direction of next move
xd, yd = random.choice(DIRS)
# set pos to current pos + direction vector
turtle.goto(px + xd, py + yd)

Categories

Resources