I have implemented the Breadth-First Search for 8-Puzzle however I want to make it more efficient by keeping an account of the visited states/nodes.
If the state has not been visited
then append it to visit and continue to expand that state/node
else state was the visit
then go to the next state/node
I am having trouble only appending the visit nodes and moving to a node that has not been visited.
Here is my code:
import copy
from random import randint
def bfs(puzzle):
solution = []
#initialization
goal = [0,1,2,3,4,5,6,7,8]
#possible combination move
possible_move = [[1,3],[0,2,4],[1,5],[0,4,6],[1,3,5,7],[2,4,8],[3,7],[4,6,8],[5,7]]
node = Node(puzzle)
queue = [node]
#move check
move = 0
loop = True
while loop:
node = queue.pop(0)
print('\nthe state of this game position is:\n ' + str(node.state))
if node.state == goal:
loop = False
break
blank = node.state.index(8)
print('the index of the blank is '+ str(blank))
possible_pos = possible_move[blank]
print('possible pos '+ str(possible_pos))
for i in possible_pos:
possible_sw = node.state[:]
print('index swap = '+ str(i))
possible_sw[blank] = possible_sw[i]
possible_sw[i] = 8
print('the child node is ' + str(possible_sw))
#appending the solution
queue.append(Node(possible_sw, node))
#Test to check if it will show all the visited nodes
visit = (queue)
for node in visit:
if node in visit:
print('there is a macth ' + str(node.state))
else:
print('there is no macth move on!')
while node.parent:
solution.append(node.state.index(8))
node = node.parent
move += 1
print('moves made '+ str(move))
solution.reverse()
print('moves list '+ str(solution))
return solution
When marking nodes as visited, you should not reset visit in each iteration. It should be a collection that is initialised once, at the start of the search, and then extends as nodes are visited.
For this you could use a set. To add states to that set, these states must be hashable, so I would suggest turning them into tuples.
You can decide to mark a node as visited when you pull it from the queue, or, alternatively, you can mark a node as visited when you are about to push it on the queue -- and if it is already visited, not push it unto it. Below I will go for the first approach. If you go for the second, then also mark the initial node as visited before the loop starts.
Not an issue, but here are some other remarks:
There is no need for the loop name. Just use an unconditional loop (while True) from which you break.
There is no need for the move name. You can know the number of moves with the len function
The adapted code:
def bfs(puzzle):
#initialization
visited = set() # Initialise only once
solution = []
goal = [0,1,2,3,4,5,6,7,8]
#possible combination move
possible_move = [[1,3],[0,2,4],[1,5],[0,4,6],[1,3,5,7],[2,4,8],[3,7],[4,6,8],[5,7]]
node = Node(puzzle)
queue = [node]
while True:
node = queue.pop(0)
print(f'the state of this game position is:\n {node.state}')
if node.state == goal:
break
key = tuple(node.state) # Make state usuable for adding in a set
if key in visited:
continue # Don't visit twice
visited.add(key)
blank = node.state.index(8)
possible_pos = possible_move[blank]
for i in possible_pos:
possible_sw = node.state[:]
possible_sw[blank] = possible_sw[i]
possible_sw[i] = 8
queue.append(Node(possible_sw, node))
while node.parent:
solution.append(node.state.index(8))
node = node.parent
print(f'moves made {len(solution)}')
solution.reverse()
print(f'moves list {solution}')
return solution
Related
I'm currently making a "match stick" game in CLI and It's Player vs AI. Almost everything works properly except that the "AI" chooses a stick that has been previously removed.
Here is the code:
class CPyramide(object):
def __init__(self, lines):
self.pir = []
for i in range(lines):
sticks = ["|" for j in range(i+1)]
self.pir.append(sticks)
def __str__(self):
o = ""
for i, L in enumerate(self.pir):
spaces = (len(self.pir) - i) * " "
o += spaces
o += " ".join(L)
o += "\n"
return o
def stickDelete(self, line, n):
self.pir[line] = self.pir[line][n:]
try:
lines = int(sys.argv[1])
sticks = int(sys.argv[2])
cpir = CPyramide(lines)
print(cpir)
while True:
print("Your turn:")
inputLine = input("Line: ")
inputSticks = input("Matches: ")
if int(inputSticks) > sticks:
print("Error: you cannot remove more than",sticks,"matches per turn")
continue
else:
print("Player removed",inputSticks,"match(es) from line",inputLine)
cpir.stickDelete(int(inputLine) - 1,int(inputSticks))
print(cpir)
print("AI's turn...")
aiSticks = randint(1,sticks)
aiLines = randint(1,lines)
print("AI removed",aiSticks,"match(es) from line",aiLines)
cpir.stickDelete(aiLines - 1,aiSticks)
print(cpir)
I've been trying to make it so it checks every array that contains a [|] and possibly remove it but I don't know how I can make it.
Is there a way I can make the function cpir.stickDelete checks if an array has a [|] in it and possibly remove it but randomly? Because everytime I'm playing with the AI it just chooses something that has already been previously removed.
Is there a way to check through every array and possibly check if it contains a [|] and remove it but randomly?
Thanks for reading.
Try this :
if "|" in my_list:
my_list.remove("|")
import random
welcomes = ["Hello","Hi","What's up","YO", "Piss off"]
chosen = random.choice(welcomes)
print("You have entered the welcome cave ----- {} -----".format(chosen))
How do I make sure that Hello for example isn't repeated twice in a row? It's fine if they're repeated again later, just not straight after.
Use random.sample instead of random.choice. Find online demo
import random
welcomes = ["Hello","Hi","What's up","YO", "Piss off"]
chosen = random.sample(welcomes,2)
for item in chosen:
print("You have entered the welcome cave ----- {} -----".format(item))
If you want to generate a very long stream of greetings having the property: no consecutive greetings are the same (Online demos: last version):
import random
def random_hello():
welcomes = ["Hello", "Hi", "What's up", "YO", "Piss off"]
last_hello = None
while 1:
random.shuffle(welcomes)
if welcomes[0] == last_hello:
continue
for item in welcomes:
yield item
last_hello = welcomes[-1]
hellower = iter(random_hello())
for _ in range(1000):
print(next(hellower))
Or when you worry about deterministic time, swap elements (with 1st):
if welcomes[0] == last_hello:
welcomes[0], welcomes[1] = welcomes[1], welcomes[0]
or random:
if welcomes[0] == last_hello:
swap_with = random.randrange(1, len(welcomes))
welcomes[0], welcomes[swap_with] = welcomes[swap_with], welcomes[0]
The use of random.sample like other answers suggested is only useful when you know you'll only need a certain number of items, like 2. The best way to ensure randomness and no repeats is to use random.shuffle:
import random
welcomes = ["Hello","Hi","What's up","YO", "Piss off"]
random.shuffle(welcomes)
Which well shuffle the list in-place, and then you can just start to pop items away from the list, until it's done:
while len(welcomes)>0:
print("You have entered the welcome cave ----- {} -----".format(welcomes.pop())
That will work for a list of any length, and you can use this process until the entire list is done. You can also add another loop around the whole process if you want to keep it going forever and not just until the list is over.
You could do it with a hit&miss approach:
import random
class RandomChoiceNoImmediateRepeat(object):
def __init__(self, lst):
self.lst = lst
self.last = None
def choice(self):
if self.last is None:
self.last = random.choice(self.lst)
return self.last
else:
nxt = random.choice(self.lst)
# make a new choice as long as it's equal to the last.
while nxt == self.last:
nxt = random.choice(self.lst)
# Replace the last and return the choice
self.last = nxt
return nxt
One chould refine it with random.choices and weights (requires python-3.6) but that approach should work for all python versions:
>>> welcomes = ["Hello","Hi","What's up","YO", "Piss off"]
>>> gen = RandomChoiceNoImmediateRepeat(welcomes)
>>> gen.choice()
'YO'
Or if you don't like hit&miss you can also draw a random index between 0 and the length of the list - 2 and add 1 if it's equal or higher than the previous one. That ensures that no repeats can happen and only requires one call to random to get the next choice:
import random
class RandomChoiceNoImmediateRepeat(object):
def __init__(self, lst):
self.lst = lst
self.lastidx = None
def choice(self):
if self.lastidx is None:
nxtidx = random.randrange(0, len(self.lst))
else:
nxtidx = random.randrange(0, len(self.lst)-1)
if nxtidx >= self.lastidx:
nxtidx += 1
self.lastidx = nxtidx
return self.lst[nxtidx]
My take: We create two identical lists. In a loop we pop one value from one list and if the length of that list is smaller than the original list - 1 we reset the list to its original state:
import random
origin = ["Hello","Hi","What's up","YO", "Piss off"]
welcomes = origin.copy()
for i in range(5):
if len(welcomes) < len(origin) - 1:
welcomes = origin.copy()
random.shuffle(welcomes) # shuffle
chosen = welcomes.pop() # pop one value
print("You have entered the welcome cave ----- {} -----".format(chosen))
E.g output with 5 loops:
You have entered the welcome cave ----- Piss off -----
You have entered the welcome cave ----- YO -----
You have entered the welcome cave ----- Piss off -----
You have entered the welcome cave ----- YO -----
You have entered the welcome cave ----- What's up -----
Put [a = list.consumableList] instead of just the list in the = output
(replace the lowercase list in the brackets with the name of your list)
I'm working on an artificial intelligence program to play a board game. The trouble is that my implementation of the minimax algorithm has a serious error somehow that I've been trying to figure for a couple of days now and still haven't managed to figure it out.
It's doing my nut in so I'd appreciate it if someone could have a look at my code and see why it's not terminating properly.
When I run it it appears to correctly search the tree and terminate when it reaches the final depth but for some reason it can't search the of the deepest layer and forever finds more nodes to search (which is theoretically impossible).
I have covered all the rest of the code with unittests and it's all working. However my unittests on this part don't ever work because it just endlessly executes without terminating.
If there is any information that can help I can provide it.
Thanks
def recursive_minimax(self, board, depth, player_id):
#there are no more moves possible so the game is over
if board.get_available_moves(player_id)==[]:
return 1 if player_id == self.max_player else -1
#check to ensure correct depth has been passed into method
if depth>self.depth:
raise ValueError("Impossible value for depth")
#if max depth reached calculate estimated node value
if depth == 0:
return self.eval_func(board, player_id)
if player_id == self.max_player:
best_val = -999
moves = board.get_available_moves(player_id)
for move in moves:
new_board = board.make_move(move, player_id, True)
opposing_player = board.get_opposing_player(player_id)
val = self.recursive_minimax(new_board, depth - 1, opposing_player)
best_val = max(val, best_val)
else:
best_val = 999
moves = board.get_available_moves(player_id)
for move in moves:
#make copy of the board and play move
new_board = board.make_move(move, player_id, True)
opposing_player = board.get_opposing_player(player_id)
val = self.recursive_minimax(new_board, depth - 1, opposing_player)
best_val = min(val, best_val)
return best_val
Here is the unittest (forgive my sloppiness in the test code):
def test_recursive_minimax(self):
#self.skipTest("recursive error")
init_board = _make_board(5)
for tile_id in init_board.keys():
counters = choice(range(len(init_board[tile_id]["neighbours"])-1))
init_board[tile_id]["counters"] = counters if counters!= 0 else None
init_board[tile_id]["player"] = choice([0,1]) if counters!=0 else None
board=Board(init_board, 0)
strategy = Minimax(board)
for tile_id, tile in strategy.board.tiles():
player = 0 if tile["player"]==None else tile["player"]
new_board = strategy.board.make_move(tile_id, player)
value = strategy.recursive_minimax(new_board, 1,player)
self.assertIsNotNone(value)
self.assertTrue(0<=value<=1 or value in(999, -999))
for tile_id, tile in strategy.board.tiles():
player = 0 if tile["player"]==None else tile["player"]
new_board = strategy.board.make_move(tile_id, player)
value = strategy.recursive_minimax(new_board, 2,player)
self.assertIsNotNone(value)
self.assertTrue(0<=value<=1 or value in(999, -999))
for tile_id, tile in strategy.board.tiles():
player = 0 if tile["player"]==None else tile["player"]
new_board = strategy.board.make_move(tile_id, player)
value = strategy.recursive_minimax(new_board, 3,player)
self.assertIsNotNone(value)
self.assertTrue(0<=value<=1 or value in(999, -999))
the unittest never gets to the assertion because the minimax doesn't terminate
I have looked online everywhere and I am yet to find how to create a basic random dungeon generation technique.
I have been experimenting myself by randomising a list of numbers and allocating them to width height and position etc. but I just can't seem to get it working.
It would be great if someone could direct me to a website or maybe point me in the right direction, I am a little inexperienced. Thanks in advance.
Here's an example of a random dungeon generator, taken from RogueBasin, which has a lot of articles on that topic and on rogue-likes in general:
# Class to produce random map layouts
from random import *
from math import *
class dMap:
def __init__(self):
self.roomList=[]
self.cList=[]
def makeMap(self,xsize,ysize,fail,b1,mrooms):
"""Generate random layout of rooms, corridors and other features"""
# makeMap can be modified to accept arguments for values of failed, and percentile of features.
# Create first room
self.size_x = xsize
self.size_y = ysize
# initialize map to all walls
self.mapArr=[]
for y in range(ysize):
tmp = []
for x in range(xsize):
tmp.append(1)
self.mapArr.append( tmp )
w,l,t=self.makeRoom()
while len(self.roomList)==0:
y=randrange(ysize-1-l)+1
x=randrange(xsize-1-w)+1
p=self.placeRoom(l,w,x,y,xsize,ysize,6,0)
failed=0
while failed<fail: #The lower the value that failed< , the smaller the dungeon
chooseRoom=randrange(len(self.roomList))
ex,ey,ex2,ey2,et=self.makeExit(chooseRoom)
feature=randrange(100)
if feature<b1: #Begin feature choosing (more features to be added here)
w,l,t=self.makeCorridor()
else:
w,l,t=self.makeRoom()
roomDone=self.placeRoom(l,w,ex2,ey2,xsize,ysize,t,et)
if roomDone==0: #If placement failed increase possibility map is full
failed+=1
elif roomDone==2: #Possiblilty of linking rooms
if self.mapArr[ey2][ex2]==0:
if randrange(100)<7:
self.makePortal(ex,ey)
failed+=1
else: #Otherwise, link up the 2 rooms
self.makePortal(ex,ey)
failed=0
if t<5:
tc=[len(self.roomList)-1,ex2,ey2,t]
self.cList.append(tc)
self.joinCorridor(len(self.roomList)-1,ex2,ey2,t,50)
if len(self.roomList)==mrooms:
failed=fail
self.finalJoins()
def makeRoom(self):
"""Randomly produce room size"""
rtype=5
rwide=randrange(8)+3
rlong=randrange(8)+3
return rwide,rlong,rtype
def makeCorridor(self):
"""Randomly produce corridor length and heading"""
clength=randrange(18)+3
heading=randrange(4)
if heading==0: #North
wd=1
lg=-clength
elif heading==1: #East
wd=clength
lg=1
elif heading==2: #South
wd=1
lg=clength
elif heading==3: #West
wd=-clength
lg=1
return wd,lg,heading
def placeRoom(self,ll,ww,xposs,yposs,xsize,ysize,rty,ext):
"""Place feature if enough space and return canPlace as true or false"""
#Arrange for heading
xpos=xposs
ypos=yposs
if ll<0:
ypos+=ll+1
ll=abs(ll)
if ww<0:
xpos+=ww+1
ww=abs(ww)
#Make offset if type is room
if rty==5:
if ext==0 or ext==2:
offset=randrange(ww)
xpos-=offset
else:
offset=randrange(ll)
ypos-=offset
#Then check if there is space
canPlace=1
if ww+xpos+1>xsize-1 or ll+ypos+1>ysize:
canPlace=0
return canPlace
elif xpos<1 or ypos<1:
canPlace=0
return canPlace
else:
for j in range(ll):
for k in range(ww):
if self.mapArr[(ypos)+j][(xpos)+k]!=1:
canPlace=2
#If there is space, add to list of rooms
if canPlace==1:
temp=[ll,ww,xpos,ypos]
self.roomList.append(temp)
for j in range(ll+2): #Then build walls
for k in range(ww+2):
self.mapArr[(ypos-1)+j][(xpos-1)+k]=2
for j in range(ll): #Then build floor
for k in range(ww):
self.mapArr[ypos+j][xpos+k]=0
return canPlace #Return whether placed is true/false
def makeExit(self,rn):
"""Pick random wall and random point along that wall"""
room=self.roomList[rn]
while True:
rw=randrange(4)
if rw==0: #North wall
rx=randrange(room[1])+room[2]
ry=room[3]-1
rx2=rx
ry2=ry-1
elif rw==1: #East wall
ry=randrange(room[0])+room[3]
rx=room[2]+room[1]
rx2=rx+1
ry2=ry
elif rw==2: #South wall
rx=randrange(room[1])+room[2]
ry=room[3]+room[0]
rx2=rx
ry2=ry+1
elif rw==3: #West wall
ry=randrange(room[0])+room[3]
rx=room[2]-1
rx2=rx-1
ry2=ry
if self.mapArr[ry][rx]==2: #If space is a wall, exit
break
return rx,ry,rx2,ry2,rw
def makePortal(self,px,py):
"""Create doors in walls"""
ptype=randrange(100)
if ptype>90: #Secret door
self.mapArr[py][px]=5
return
elif ptype>75: #Closed door
self.mapArr[py][px]=4
return
elif ptype>40: #Open door
self.mapArr[py][px]=3
return
else: #Hole in the wall
self.mapArr[py][px]=0
def joinCorridor(self,cno,xp,yp,ed,psb):
"""Check corridor endpoint and make an exit if it links to another room"""
cArea=self.roomList[cno]
if xp!=cArea[2] or yp!=cArea[3]: #Find the corridor endpoint
endx=xp-(cArea[1]-1)
endy=yp-(cArea[0]-1)
else:
endx=xp+(cArea[1]-1)
endy=yp+(cArea[0]-1)
checkExit=[]
if ed==0: #North corridor
if endx>1:
coords=[endx-2,endy,endx-1,endy]
checkExit.append(coords)
if endy>1:
coords=[endx,endy-2,endx,endy-1]
checkExit.append(coords)
if endx<self.size_x-2:
coords=[endx+2,endy,endx+1,endy]
checkExit.append(coords)
elif ed==1: #East corridor
if endy>1:
coords=[endx,endy-2,endx,endy-1]
checkExit.append(coords)
if endx<self.size_x-2:
coords=[endx+2,endy,endx+1,endy]
checkExit.append(coords)
if endy<self.size_y-2:
coords=[endx,endy+2,endx,endy+1]
checkExit.append(coords)
elif ed==2: #South corridor
if endx<self.size_x-2:
coords=[endx+2,endy,endx+1,endy]
checkExit.append(coords)
if endy<self.size_y-2:
coords=[endx,endy+2,endx,endy+1]
checkExit.append(coords)
if endx>1:
coords=[endx-2,endy,endx-1,endy]
checkExit.append(coords)
elif ed==3: #West corridor
if endx>1:
coords=[endx-2,endy,endx-1,endy]
checkExit.append(coords)
if endy>1:
coords=[endx,endy-2,endx,endy-1]
checkExit.append(coords)
if endy<self.size_y-2:
coords=[endx,endy+2,endx,endy+1]
checkExit.append(coords)
for xxx,yyy,xxx1,yyy1 in checkExit: #Loop through possible exits
if self.mapArr[yyy][xxx]==0: #If joins to a room
if randrange(100)<psb: #Possibility of linking rooms
self.makePortal(xxx1,yyy1)
def finalJoins(self):
"""Final stage, loops through all the corridors to see if any can be joined to other rooms"""
for x in self.cList:
self.joinCorridor(x[0],x[1],x[2],x[3],10)
# ----------------------------------------------------------------------------
# Main
# ----------------------------------------------------------------------------
startx=20 # map width
starty=10 # map height
themap= dMap()
themap.makeMap(startx,starty,110,50,60)
for y in range(starty):
line = ""
for x in range(startx):
if themap.mapArr[y][x]==0:
line += "."
if themap.mapArr[y][x]==1:
line += " "
if themap.mapArr[y][x]==2:
line += "#"
if themap.mapArr[y][x]==3 or themap.mapArr[y][x]==4 or themap.mapArr[y][x]==5:
line += "="
print line
There are a lot of different methods to create a random dungeon, a common one is using Binary space partitioning (BSP).
Also make sure to take a look at libtcod, which offers also an implementation of that algorithm.
Context
I'm currently attempting Reddit's /r/dailyprogrammer challenge.
The idea is to find a solution to an ASCII maze. Unfortunately the recursion is working differently than I expected. The program checks if there is space to move to the right, left, below or above the current space. If there is then the space is moved to and the function is entered again with new co-ordinaries. This continues until the end is found.
When the end is found the program exits. If a dead end is found then the recursion will back up to the previous point and check for any more directions, this continues until the end.
Problem
My program works great however the maze draws my lines (represented by '*****') even after the recursion is backed up. I don't know how to explain that so I'll use images to give a better description.
Each new color represents a new path. However I would have expected only the current recursions path to show. For example in this case I would expect only the yellow path to show. Can someone help me understand why all paths remain?
The code
import time
import sys
import os
maze = """\
###############
#S # #
### ### ### # #
# # # # #
# ##### ##### #
# # # #
# ### # ### ###
# # # # # #
# # ### # ### #
# # # # # # #
### # # # # # #
# # # # # #
# ####### # # #
# #E#
###############"""
def displayMaze(maze):
os.system("cls")
display = ""
for x in maze:
for y in x:
display = display + y
display = display + "\n"
print(display)
def findStart(maze):
#Get the maze start co-ords.
for x in range(0,len(maze[0])):
for y in range(0,len(maze)):
if maze[x][y] == "S":
return x,y
def findPath(x,y,maze):
#Look right, left, up and down, If path then move.
time.sleep(0)
if maze[y][x+1] == " ":
newMaze = maze
newMaze[y][x+1] = "*"
displayMaze(newMaze)
findPath(x+1,y,newMaze)
elif maze[y][x+1] == "E":
sys.exit("Done")
if maze[y][x-1] == " ":
newMaze = maze
newMaze[y][x-1] = "*"
displayMaze(newMaze)
findPath(x-1,y,newMaze)
elif maze[y][x-1] == "E":
sys.exit("Done")
if maze[y+1][x] == " ":
newMaze = maze
newMaze[y+1][x] = "*"
displayMaze(newMaze)
findPath(x,y+1,newMaze)
elif maze[y+1][x] == "E":
sys.exit("Done")
if maze[y-1][x] == " ":
newMaze = maze
newMaze[y-1][x] = "*"
displayMaze(newMaze)
findPath(x,y-1,newMaze)
elif maze[y-1][x] == "E":
sys.exit("Done")
if __name__ == "__main__":
maze = maze.split("\n")
newMaze = []
for line in maze:
newMaze.append(list(line))
x,y = findStart(newMaze)
findPath(x,y,newMaze)
newMaze = maze doesn't copy the list, it just creates another name pointing to the same object. To copy, you should import copy at the top of your program, then do newMaze = copy.deepcopy(maze). (You need a deep copy because maze is a list of lists, so you need to copy not only the outer list, but all the lists inside it too.)
In Python, assignment to a plain name (like blah = ...) never copies anything. If you want a copy, you must make one explicitly. The way to do that depends on what you're copying.