How to implement overwriting in my current "Redo" function in Python? - python

I have (along with the help of my buddy #Nuclearman who answered one of my other questions) partly implemented a redo function in my program, which is a turtle graphics program that draws any letter pressed on the keyboard pressed by the user onto the turtle graphics canvas. I say "partly" because although it does redo, it does not do what it was meant for, which is overwrite the last undone letter with a new one if one is called by the user. For example, suppose you wrote "HELLO" on the canvas and undid up to "H". You then wanted to replace the "E", with, lets say, an "A". So you draw an "A". When you press redo, it is supposed to now show "HAL" right? Well, that is not the case with my redo. My redo instead, following the example I gave, outputs this:
It draws the E over the A! Well, that is not what the redo is meant for! So, how could I make so that the turtle would draw "HAL" instead of whatever is happening in the image above and implement this based on my current redo function shown below? Any help is greatly appreciated! :)
My Redo function:
def Clear():
# Clear the canvas
clear()
speed(0)
tracer(0,0)
def redoHandler():
if undoHandler.handling == True and draw.drawing == True and len(newerdeq) > 0:
# "newerdeq" is my 'forward' stack for the redo to be possible
redoHandler.handling = True
if not hasattr(redoHandler, "counter"):
redoHandler.counter = 0
redoHandler.counter += 1
draw.counter += 1
print("`draw` has been called {} times.".format(draw.counter))
Clear()
ui = newerdeq.pop() #<-- Pop redone items...
function.append(ui) #<-- back to the "undo" function stack.
penup()
try:
goto(o,p)
except:
goto(-200, 100)
pendown()
try:
# Now execute all the items (from an earlier queue that all letter functions go to first) if defined as a Point class or as a function
for i in function:
k = i.getXY()
penup()
goto(k)
pendown()
hk = i.getletterheight()
global letter_height
letter_height = hk
rk = i.getletterwidth()
global letter_width
letter_width = rk
hw = i.getwidth()
width(hw)
op = i.getcolor()
try:
color(op)
except:
for g in colors:
cp = g.getcolor2()
colormode(255)
color(cp)
j = i.getfunction()
j()
except:
i()
update()

Well, I solved it. It was a very simple fix. All I had to do was in the queue that each letter is executed, just put:
if len(newerdeq) > 0:
newerdeq.pop()
Like this:
def draw(x):
draw.drawing = True
if not hasattr(draw, 'counter'):
draw.counter = 0
global drawing
q.put(x)
process = False
drawingLock.acquire()
if not drawing:
process = True
drawing = True
drawingLock.release()
if process:
if not q.empty():
v = xcor()
y = ycor()
c = pencolor()
w = width()
ph = letter_height
pw = letter_width
x()
po = Point(v,y,c,w,isdown(),x,ph,pw)
function.append(po)
if len(newerdeq) > 0:
newerdeq.pop() #<-- Here it is... fixed.
draw.counter += 1
print("`draw` has been called {} times.".format(draw.counter))
if x == draw_W:
draw_W.drawing = True
draw_W.counter += 1
draw.counter -= 1
elif x == draw_Y:
draw_Y.drawing = True
draw_Y.counter += 1
draw.counter -= 1
global h
h = (x)
drawingLock.acquire()
drawing = False
drawingLock.release()

Related

Negamax Not Working For Python Chess Engine

I'm making a very simple Python chess engine using the standard Python chess library with a very simple evaluation function; the sum of the total black piece weights (positive) plus the sum of the total white piece weights (negative). The engine always plays as black.
I used the Negamax Wikipedia page for guidance and the depth is to the fourth ply. I don't expect grandmaster performance, but the engine makes very questionable moves, for example: e2e4 and f1c4 for white causes the engine to freely give up it's pawn via b7b5.
Can anyone help me out? I'm completely lost as to what I did wrong. The negamax (called search) and the evaluation function is shown below:
import chess
import time
import math
from time import sleep
from chessboard import display
scoreMovePair = {}
def colorMap(color):
if color == True:
return -1
return 1
def pieceMap(pieceNum):
if pieceNum == 1:
return 1
elif pieceNum == 2:
return 3
elif pieceNum == 3:
return 3
elif pieceNum == 4:
return 5
elif pieceNum == 5:
return 9
return pieceNum
def posEval(board):
score = 0
for i in range(0, 64):
piece = board.piece_at(i)
if piece != None:
score = score + pieceMap(piece.piece_type)*colorMap(piece.color)
return score
def search(board, level, a, b, moveSet, color):
if level == 4:
score = posEval(board)
scoreMovePair[score] = moveSet[0]
return score*color
if board.is_checkmate():
return 1000*colorMap(board.turn)
value = -10000
for move in board.legal_moves:
board.push(move)
moveSet.append(move)
value = max(value, -search(board, level + 1, -b, -a, moveSet, -color))
a = max(a, value)
moveSet.pop()
board.pop()
if (a >= b):
break
return value
def main():
global scoreMovepair
board = chess.Board()
display.start(board.fen())
while not display.checkForQuit():
validMoves = list(board.legal_moves)
if len(validMoves) == 0:
break
else:
move = input("Enter move: ")
t0 = time.time()
move = str(move)
myMove = chess.Move.from_uci(move)
if myMove in validMoves:
board.push_san(move)
value = search(board, 0, -10000, 10000, [], 1)
move = scoreMovePair[value]
print(scoreMovePair)
print("FINAL -> "+str(value))
board.push(move)
print(board.fen())
display.update(board.fen())
sleep(1)
t1 = time.time()
print(t1-t0)
else:
continue
display.terminate()
if __name__ == "__main__":
main()
Just based on a first glance, I would say you may be missing a "quiescence search" (meaning a search for quietness). Also called "captures only search".
https://www.chessprogramming.org/Quiescence_Search
This is a search that is called instead of an evaluation function on your leaf nodes (nodes where max depth is reached). The search makes only capture moves until there are no more captures (with unlimited depth).
In short, without this search, whoever gets the last move in the search (determined by depth) will be able to do anything without consequences. This can lead to some weird results.

How to nest a function that responds to outside of function call?

I'm trying to figure out how I can solve this issue. I have a Raspberry Pi that is set up with a breadboard that consists of:
1 RGB light
2 buttons (left and right)
1 OLED screen
Each component works and I can run each one. What I'm trying to do is write a script that will allow me to select the "mode" with the left button (everything off vs lights vs screen on).
When a mode is selected, the right button then allows me to select between options within that mode. Below is the code as I have it:
def off():
lights = [red,green,blue]
for light in lights:
light.off()
def lightSelector():
off()
number = 0
while number < 5:
if rightButton.is_pressed:
if number == 0:
off()
red.on()
sleep(1)
number += 1
elif number == 1:
off()
green.on()
sleep(1)
number += 1
elif number == 2:
off()
blue.on()
sleep(1)
number += 1
elif number == 3:
off()
row()
sleep(1)
number+= 1
else:
number = 0
def picture():
image = Image.open('grant.jpeg')
image_r = image.resize((width,height), Image.BICUBIC)
image_bw = image_r.convert("1")
for x in range(width):
for y in range(height):
oled.pixel(x,y,bool(int(image_bw.getpixel((x,y)))))
oled.show()
def oledOff():
oled.fill(0)
oled.show()
def buttons():
x = 0
y = 0
while y is 0:
print('x = ' , x)
print('y = ' , y)
if leftButton.is_pressed:
if x == 0 :
oledOff()
off()
sleep(0.5)
x += 1
elif x == 1:
oledOff()
off()
lightSelector()
sleep(0.5)
x += 1
elif x == 2:
oledOff()
off()
picture()
sleep(0.5)
x += 1
else:
x = 0
oledOff()
off()
buttons()
The idea is that the buttons() function is the main overall function and will call the others as needed. My issue is that once I get to y == 1, or the lightSelector() function, it no longer registers that the leftButton is being pressed and won't switch to the next mode and I'm stuck in the lightSelector() function.
I know at baseline I can spell out lightSelector within the buttons function instead of calling another function but I'm trying to not be as verbose. I don't have any experience with threading or multprocessing and looked into it but couldn't see how that would help.

Reinforcement algorithm seems to learn but script is getting stuck and agent is not resetting

Currently working on a reinforcement algorithm using Q tables and turtle graphics. The agent is within a grid of 6 squares and needs to reach the far right hand side as its goal. I have constructed this and then I run my algorithm in order for the agent to learn. I am facing the following problems. The script eventually gets stuck and as a result I can only seem to go through one episode. The agent (blue marker) is flashing around the 0,0 coordinate mark although I have set specific coordinates for it. Finally, the agent basically leaves a trail of its steps. My logic seems fine but can't pinpoint what is causing these problems
""" Basic Reinforcement Learning environment using Turtle Graphics """
#imported libraries required for this project
import turtle
import pandas as pd
import numpy as np
import time
#import numpy as np
""" Environment """
#initialise the screen using a turtle object
wn = turtle.Screen()
wn.bgcolor("black")
wn.title("Basic_Reinforcement_Learning_Environment")
#wn.bgpic("game_background.gif")
#this function initializes the 2D environment
def grid(size):
#this function creates one square
def create_square(size,color="white"):
greg.color(color)
greg.pd()
for i in range(4):
greg.fd(size)
greg.lt(90)
greg.pu()
greg.fd(size)
#this function creates a row of sqaures based on simply one square
def row(size,color="white"):
for i in range(6):
create_square(size)
greg.hideturtle()
row(size)
greg = turtle.Turtle()
greg.speed(0)
greg.setposition(-150,0)
grid(50)
def player_set(S):
player = turtle.Turtle()
player.color("blue")
player.shape("circle")
player.penup()
player.speed(0)
player.setposition(S)
player.setheading(90)
N_STATES = 6 # the length of the 1 dimensional world
ACTIONS = ['left', 'right'] # available actions
EPSILON = 0.9 # greedy police
ALPHA = 0.1 # learning rate
GAMMA = 0.9 # discount factor
MAX_EPISODES = 13 # maximum episodes
FRESH_TIME = 0.3 # fresh time for one move
#this functions builds a Q-table and initializes all values to 0
def build_q_table(n_states, actions):
table = pd.DataFrame(
np.zeros((n_states, len(actions))), # q_table initial values
columns=actions, # actions's name
)
# print(table) # show table
return table
def choose_action(state, q_table):
# This is how to choose an action
state_actions = q_table.iloc[state, :]
# act non-greedy or state-action have no value
if (np.random.uniform() > EPSILON) or ((state_actions == 0).all()):
action_name = np.random.choice(ACTIONS)
else: # act greedy
# replace argmax to idxmax as argmax means a different function
action_name = state_actions.idxmax()
return action_name
def get_env_feedback(S, A):
# This is how agent will interact with the environment
if A == 'right': # move right
if S == N_STATES - 2: # terminate
S_ = 'terminal'
R = 1
else:
S_ = S + 1
R = 0
else: # move left
R = 0
if S == 0:
S_ = S # reach the wall
else:
S_ = S - 1
return S_, R
def update_env(S, episode, step_counter):
coords = [(-125,25),(-75,25),(-25,25),(25,25),(75,25),(125,25)]
if S == 'terminal':
interaction = 'Episode %s: total_steps = %s' %(episode+1, step_counter)
print('\r{}'.format(interaction), end='')
time.sleep(2)
print('\r', end='')
else:
player_set(coords[S])
time.sleep(FRESH_TIME)
def rl():
q_table = build_q_table(N_STATES, ACTIONS)
for episode in range(MAX_EPISODES):
step_counter = 0
S = 0
is_terminated = False
update_env(S, episode, step_counter)
while not is_terminated:
A = choose_action(S, q_table)
S_, R = get_env_feedback(S,A)
q_predict = q_table.loc[S,A]
if S_ != 'terminal':
q_target = R + GAMMA * q_table.iloc[S_, :].max()
else:
q_target = R
is_terminated = True
q_table.loc[S, A] += ALPHA * (q_target - q_predict)
S = S_
update_env(S, episode, step_counter+1)
step_counter += 1
return q_table
rl()
CHANGES: updated the return statement and the algorithm now works so that it goes through 13 episodes!!! HOWEVER, I cannot seem to implement the player token (agent) so that it doesn't leave a trail of all steps taken and I would like it to reset after each episode. This maybe an issue to do with scope:
final solution:
""" Basic Reinforcement Learning environment using Turtle Graphics """
#imported libraries required for this project
import turtle
import pandas as pd
import numpy as np
import time
#import numpy as np
""" Environment """
#initialise the screen using a turtle object
wn = turtle.Screen()
wn.bgcolor("black")
wn.title("Basic_Reinforcement_Learning_Environment")
#wn.bgpic("game_background.gif")
#this function initializes the 2D environment
def grid(size):
#this function creates one square
def create_square(size,color="white"):
greg.color(color)
greg.pd()
for i in range(4):
greg.fd(size)
greg.lt(90)
greg.pu()
greg.fd(size)
#this function creates a row of sqaures based on simply one square
def row(size,color="white"):
for i in range(6):
create_square(size)
greg.hideturtle()
row(size)
greg = turtle.Turtle()
greg.speed(0)
greg.setposition(-150,0)
grid(50)
player = turtle.Turtle()
player.color("blue")
player.shape("circle")
player.penup()
player.speed(0)
player.setheading(90)
def player_set(S):
player.setposition(S)
N_STATES = 6 # the length of the 1 dimensional world
ACTIONS = ['left', 'right'] # available actions
EPSILON = 0.9 # greedy police
ALPHA = 0.1 # learning rate
GAMMA = 0.9 # discount factor
MAX_EPISODES = 13 # maximum episodes
FRESH_TIME = 0.3 # fresh time for one move
#this functions builds a Q-table and initializes all values to 0
def build_q_table(n_states, actions):
table = pd.DataFrame(
np.zeros((n_states, len(actions))), # q_table initial values
columns=actions, # actions's name
)
# print(table) # show table
return table
def choose_action(state, q_table):
# This is how to choose an action
state_actions = q_table.iloc[state, :]
# act non-greedy or state-action have no value
if (np.random.uniform() > EPSILON) or ((state_actions == 0).all()):
action_name = np.random.choice(ACTIONS)
else: # act greedy
# replace argmax to idxmax as argmax means a different function
action_name = state_actions.idxmax()
return action_name
def get_env_feedback(S, A):
# This is how agent will interact with the environment
if A == 'right': # move right
if S == N_STATES - 2: # terminate
S_ = 'terminal'
R = 1
else:
S_ = S + 1
R = 0
else: # move left
R = 0
if S == 0:
S_ = S # reach the wall
else:
S_ = S - 1
return S_, R
def update_env(S, episode, step_counter):
coords = [(-125,25),(-75,25),(-25,25),(25,25),(75,25),(125,25)]
if S == 'terminal':
interaction = 'Episode %s: total_steps = %s' %(episode+1, step_counter)
print('\n{}'.format(interaction), end='')
time.sleep(2)
print('\r', end='')
else:
player_set(coords[S])
time.sleep(FRESH_TIME)
def rl():
q_table = build_q_table(N_STATES, ACTIONS)
for episode in range(MAX_EPISODES):
step_counter = 0
S = 0
is_terminated = False
update_env(S, episode, step_counter)
while not is_terminated:
A = choose_action(S, q_table)
S_, R = get_env_feedback(S,A)
q_predict = q_table.loc[S,A]
if S_ != 'terminal':
q_target = R + GAMMA * q_table.iloc[S_, :].max()
else:
q_target = R
is_terminated = True
q_table.loc[S, A] += ALPHA * (q_target - q_predict)
S = S_
update_env(S, episode, step_counter+1)
step_counter += 1
return q_table
rl()
In the following code snippet copied from your question:
def rl():
q_table = build_q_table(N_STATES, ACTIONS)
for episode in range(MAX_EPISODES):
step_counter = 0
S = 0
is_terminated = False
update_env(S, episode, step_counter)
while not is_terminated:
# ...
# <snip>
# ...
return q_table
your rl() function has a return statement after the while loop going through the timesteps for a single episode, inside the for loop going through the episodes. This means that your function will effectively complete only a single episode, and then already return (meaning, the rl() function is terminated) before it gets a chance to start the second episode.
Concerning this update to the question:
CHANGES: updated the return statement and the algorithm now works so that it goes through 13 episodes!!! HOWEVER, I cannot seem to implement the player token (agent) so that it doesn't leave a trail of all steps taken and I would like it to reset after each episode. This maybe an issue to do with scope
I am not 100% sure, since I'm not familiar with the turtle-graphics framework. However, I did notice that update_env() is implemented to call player_set(coords[S]) whenever the position of the player needs to be updated. This function has the following implementation:
def player_set(S):
player = turtle.Turtle()
player.color("blue")
player.shape("circle")
player.penup()
player.speed(0)
player.setposition(S)
player.setheading(90)
It looks to me like that function is creating a fresh, new player object in the new location whenever it's called, rather than updating the location of a player object that already exists. So, it looks like a completely new player object is created whenever the state is updated, and the old player object will still remain where it was. A solution would probably consist of only creating a player object once, and creating a separate function to update its position without creating a new object again.

Unexpected execution of MPI

I'm trying to parallelize a code in Python but I'm having some problems. This is the part of the code:
survivor = Survivor(32*2, 32*4)
if rank == 1:
a = 'audio/zombie_theme.ogg'
b = 'images/dungeon.jpg'
c = 'images/dead.jpg'
d = survivor.movement()
else:
a = None
b = None
c = None
d = None
a = comm.bcast(a, root=1)
b = comm.bcast(b, root=1)
c = comm.bcast(c, root=1)
d = comm.bcast(d, root=1)
if rank == 0:
pygame.mixer.music.load(a)
pygame.mixer.music.play(-1)
pygame.display.set_caption('Zombie Game')
screen = pygame.display.set_mode((display_width,display_height))
Tile.pre_init(screen)
clock = pygame.time.Clock()
dungeon = pygame.image.load(b)
keepPlaying = True
while keepPlaying:
screen.blit(dungeon, (0,0))
Zombie.spawn(total_frames, FPS)
Zombie.update(screen, survivor)
d
When I call d in the last line, shouldn't it call survivor.movement() defined in process 1? When I run the game, my main character does not move, as it's supposed to do. But, when I remove d from everywhere and put survivor.movement() in the last line, it works as expected. Could anybody help me?
d = survivor.movement
Delete curly braces. When you write d = movement() you actually assign d to whatever movement returns, not the function itself.

Solving a maze using recursion in python

So, I have an assignment which asks me to solve a maze using recursion. I will post the assignment guidelines so you can see what I am talking about. The professor didn't explain recursion that much, he gave us examples of recursion, which I will post, but I was hoping someone might be able to give me a more in depth explanation of the recursion, and how I would apply this to solving a maze. I'm not asking for anyone to write the code, I'm just hoping some explanations would put me on the right path. Thank you to anyone who answers.
Here are the examples I have:
def foo():
print("Before")
bar()
print("After")
def bar():
print("During")
def factorial(n):
"""n!"""
product = 1
for i in range(n,0,-1):
product *= i
return product
def recFac(n):
"""n! = n * (n-1)!"""
if(n == 1):
return 1
return n * recFac(n-1)
def hello():
"""Stack overflow!"""
hello()
def fib(n):
"""f(n) = f(n-1) + f(n-2)
f(0) = 0
f(1) = 1"""
if n == 0 or n == 1: #base case
return n
return fib(n-1) + fib(n-2) #recursive case
def mult(a,b):
"""a*b = a + a + a + a ..."""
#base case
if (b == 1):
return a
#recursive case
prod = mult(a,b-1)
prod *= a
return prod
def exp(a,b):
"""a ** b = a* a * a * a * a *.... 'b times'"""
#base case
if (b==0):
return 1
if (b == 1):
return a
#recursive case
return exp(a,b-1)*a
def pallindrome(word):
"""Returns True if word is a pallindrome, False otherwise"""
#base case
if word == "" or len(word)==1:
return True
#recursive case
if word[0] == word[len(word)-1]:
word = word[1:len(word)-1]
return pallindrome(word)
else:
return False
Here are the guidelines:
You are going to create a maze crawler capable of solving any maze you give it with the power of recursion!
Question 1 - Loading the maze
Before you can solve a maze you will have to load it. For this assignment you will use a simple text format for the maze. You may use this sample maze or create your own.
Your objective for this question is to load any given maze file, and read it into a 2-dimensional list.
E.g.: loadMaze("somemaze.maze") should load the somemaze.maze file and create a list like the following...
[['#','#','#','#','#','#','#','#','#'],
['#','S','#',' ',' ',' ','#','E','#'],
['#',' ','#',' ','#',' ',' ',' ','#'],
['#',' ',' ',' ','#',' ','#',' ','#'],
['#', #','#','#','#','#','#','#','#']]
Note that the lists have been stripped of all '\r' and '\n' characters. In order to make the next question simpler you may make this list a global variable.
Next write a function that prints out the maze in a much nicer format:
E.g.,
####################################
#S# ## ######## # # # # #
# # # # # # #
# # ##### ## ###### # ####### # #
### # ## ## # # # #### #
# # # ####### # ### #E#
####################################
Test your code with different mazes before proceeding.
Question 2 - Preparing to solve the maze
Before you can solve the maze you need to find the starting point! Add a function to your code called findStart() that will search the maze (character-by-character) and return the x and y coordinate of the 'S' character. You may assume that at most one such character exists in the maze. If no 'S' is found in the maze return -1 as both the x and y coordinates.
Test your code with the 'S' in multiple locations (including no location) before proceeding.
Question 3 - Solving the maze!
Finally, you are ready to solve the maze recursively! Your solution should only require a single method: solve(y,x)
A single instance of the solve method should solve a single location in your maze. The parameters y and x are the current coordinates to be solved. There are a few things your solve method should accomplish. It should check if it is currently solving the location of the 'E'. In that case your solve method has completed successfully. Otherwise it should try to recursively solve the space to the right. Note, your method should only try to solve spaces, not walls ('#'). If that recursion doesn't lead to the end, then try down, then left, and up. If all that fails, your code should backtrack a step, and try another direction.
Lastly, while solving the maze, your code should leave indicators of its progress. If it is searching to the right, the current location should have a '>' in place of the empty space. If searching down put a 'v'. If searching left '<', and if searching up '^'. If your code has to backtrack remove the direction arrow, and set the location back to a ' '.
Once your maze is solved print out the maze again. You should a see step-by-step guide to walking the maze.
E.g.,
main("somemaze.maze")
#########
#S# #E#
# # # #
# # # #
#########
S is at (1,1)
#########
#S#>>v#E#
#v#^#>>^#
#>>^# # #
#########
Test your code with different different start and end locations, and optionally over a variety of mazes.
Here is the code I have so far:
But the code is not actually printing the track in the maze, and I'm not sure why.
def loadMaze():
readIt = open('Maze.txt', 'r')
readLines = readIt.readlines()
global mazeList
mazeList = [list(i.strip()) for i in readLines]
def showMaze():
for i in mazeList:
mazeprint = ''
for j in i:
mazeprint = mazeprint + j
print(mazeprint)
print('\n')
def solve(x,y, mazeList):
mazeList[x][y] = "o"
#Base case
if y > len(mazeList) or x > len(mazeList[y]):
return False
if mazeList[y][x] == "E":
return True
if mazeList[y][x] != " ":
return False
#marking
if solve(x+1,y) == True: #right
mazeList[x][y]= '>'
elif solve(x,y+1) == True: #down
mazeList[x][y]= 'v'
elif solve(x-1,y) == True: #left
mazeList[x][y]= '<'
elif solve(x,y-1) == True: #up
mazeList[x][y]= '^'
else:
mazeList[x][y]= ' '
return (mazeList[x][y]!= ' ')
(Dating myself, I actually did this problem in COBOL, in high-school.)
You can think of solving the maze as taking steps.
When you take a step, the same rules apply every time. Because the same rules apply every time, you can use the exact same set of instructions for each step. When you take a step, you just call the same routine again, changing the parameters to indicate the new step. That's recursion. You break the problem down by taking it one step at a time.
Note: Some recursion solutions break the problem in half, solving each half independent of the other, that works when the two solutions are actually independent. It doesn't work here because each step (solution) depends on the previous steps.
If you hit a dead end, you back out of the dead end, until you find a step where there are still viable squares to check.
Helpful Hint: You don't mark the correct path on the way to the exit, because you don't know that the step you're taking right now is part of the path to the exit. You mark the path on the way back, when you know that each step is indeed part of the path. You can do this because each step remembers which square it was in before it took the next step.
Instead, you put a mark in each square you've tried that only says: I've been here, no need to check this one again. Clean those up before you print the solution.
Here is my solution of CodeEval's The Labirynth challenge:
import sys
sys.setrecursionlimit(5000)
class Maze(object):
FLOOR = ' '
WALLS = '*'
PATH = '+'
def __init__(self):
self.cols = 0
self.rows = 0
self.maze = []
def walk_forward(self, current_k, r, c):
self.maze[r][c] = current_k
next_k = current_k + 1
# up
if r > 1:
up = self.maze[r - 1][c]
if up != self.WALLS:
if up == self.FLOOR or int(up) > current_k:
self.walk_forward(next_k, r - 1, c)
# down
if r < self.rows - 1:
down = self.maze[r + 1][c]
if down != self.WALLS:
if down == self.FLOOR or int(down) > current_k:
self.walk_forward(next_k, r + 1, c)
# left
if c > 1:
left = self.maze[r][c - 1]
if left != self.WALLS:
if left == self.FLOOR or int(left) > current_k:
self.walk_forward(next_k, r, c - 1)
# right
if c < self.cols - 1:
right = self.maze[r][c + 1]
if right != self.WALLS:
if right == self.FLOOR or int(right) > current_k:
self.walk_forward(next_k, r, c + 1)
def walk_backward(self, r, c):
current_k = self.maze[r][c]
if not isinstance(current_k, int):
return False
self.maze[r][c] = self.PATH
up = self.maze[r - 1][c] if r > 0 else None
down = self.maze[r + 1][c] if r < self.rows - 1 else None
left = self.maze[r][c - 1] if c > 1 else None
right = self.maze[r][c + 1] if c < self.cols else None
passed = False
if up and isinstance(up, int) and up == current_k - 1:
self.walk_backward(r - 1, c)
passed = True
if down and isinstance(down, int) and down == current_k - 1:
self.walk_backward(r + 1, c)
passed = True
if left and isinstance(left, int) and left == current_k - 1:
self.walk_backward(r, c - 1)
passed = True
if right and isinstance(right, int) and right == current_k - 1:
self.walk_backward(r, c + 1)
def cleanup(self, cleanup_path=False):
for r in range(0, self.rows):
for c in range(0, self.cols):
if isinstance(self.maze[r][c], int):
self.maze[r][c] = self.FLOOR
if cleanup_path and self.maze[r][c] == self.PATH:
self.maze[r][c] = self.FLOOR
def solve(self, start='up', show_path=True):
# finding start and finish points
upper = lower = None
for c in range(0, self.cols):
if self.maze[0][c] == self.FLOOR:
upper = (0, c)
break
for c in range(0, self.cols):
if self.maze[self.rows - 1][c] == self.FLOOR:
lower = (self.rows - 1, c)
break
if start == 'up':
start = upper
finish = lower
else:
start = lower
finish = upper
self.cleanup(cleanup_path=True)
self.walk_forward(1, start[0], start[1])
length = self.maze[finish[0]][finish[1]]
if not isinstance(length, int):
length = 0
if show_path:
self.walk_backward(finish[0], finish[1])
self.cleanup(cleanup_path=False)
else:
self.cleanup(cleanup_path=True)
return length
def save_to_file(self, filename):
with open(filename, 'w') as f:
f.writelines(str(self))
def load_from_file(self, filename):
self.maze = []
with open(filename, 'r') as f:
lines = f.readlines()
for line in lines:
row = []
for c in line.strip():
row.append(c)
self.maze.append(row)
self.rows = len(self.maze)
self.cols = len(self.maze[0]) if self.rows > 0 else 0
def get_maze(self):
return copy.copy(self.maze)
def __str__(self):
as_string = u''
for row in self.maze:
as_string += u''.join([str(s)[-1] for s in row]) + "\n"
return as_string
maze = Maze()
maze.load_from_file(sys.argv[1])
maze.solve(show_path=True)
print str(maze)
import os
class Maze_Crawler:
def __init__(self):
self.maze = []
def load_maze(self, path):
rows = []
with open(path, 'r') as f:
rows = f.readlines()
for i in range(len(rows)):
self.maze.append([])
for j in range(len(rows[i])-1):
self.maze[i].append(rows[i][j])
return self.maze
def get_start_coor(self):
for i in range(len(self.maze)):
for j in range(len(self.maze[i])):
if self.maze[i][j] == 'S':
return i, j
return -1, -1
def solve_maze(self, coor):
x, y = coor
if self.maze[x][y] == '#' or self.maze[x][y] == 'X':
return False
if self.maze[x][y] == 'E':
return True
if self.maze[x][y] != 'S':
self.maze[x][y] = 'X'
if self.solve_maze((x+1, y)):
if self.maze[x][y] != 'S':
self.maze[x][y] = 'v'
elif self.solve_maze((x-1, y)):
if self.maze[x][y] != 'S':
self.maze[x][y] = '^'
elif self.solve_maze((x, y+1)):
if self.maze[x][y] != 'S':
self.maze[x][y] = '>'
elif self.solve_maze((x, y-1)):
if self.maze[x][y] != 'S':
self.maze[x][y] = '<'
else:
return False
return True
def show_solution(self):
for i in range(len(self.maze)):
r = ''
for j in range(len(self.maze[i])):
if self.maze[i][j] == 'X':
r += ' '
else:
r += self.maze[i][j]
print(r)
Maze solving with python shows my answer. However, if you want to do the code yourself the steps are.
1. Start at the entrance.
2. Call the function solve(x,y) with the entrance co-ordinates
3. in solve, return false if the input point has already been handled or is a wall.
4. Mark the current point as handled (tag = 'o')
5. go to the right and call solve on that point. If it returns true, set tag to '>'
6 elif do the same for left and '<'
7 elif do the same for up and '^'
8 elif do the same for down and 'v'
9 else this is a false path, set tag = ' '
10 set the current maze point to tag
11 return (tag != ' ')
Alternatively leave step 9 out and make step 11
return(tag != 'o')
Then search through the maze and replace every 'o' with ' '
You can display the maze both ways so that it will show how you tried to solve it as well as the final answer. This has been used as a Solaris screensaver with the potential paths showing in one color and the actual path in a different color so that you can see it trying and then succeeding.
Recursion is actually a simple idea: to solve a problem, you shrink the problem by one step, then solve the reduced problem. This continues until you reach a "base problem" that you know how to solve completely. You return the base solution, then add to the solution returned at each step until you have the full solution.
So to solve n!, we remember n and solve for (n-1)!. The base case is 1!, for which we return 1; then at each return step we multiply by the remembered number (2 * 1! is 2, 3 * 2! is 6, 4 * 3! is 24, 5 * 4! is 120) until we multiply by n and have the full solution. This is actually a pretty pale and anemic sort of recursion; there is only one possible decision at each step. Known as "tail recursion", this is very easy to turn inside-out and convert to an iterative solution (start at 1 and multiply by each number up to n).
A more interesting sort of recursion is where you split the problem in half, solve each half, then combine the two half-solutions; for example quicksort sorts a list by picking one item, dividing the list into "everything smaller than item" and "everything bigger than item", quicksorting each half, then returning quicksorted(smaller) + item + quicksorted(larger). The base case is "when my list is only one item, it is sorted".
For the maze, we are going to split the problem four ways - all solutions possible if I go right, left, up, and down from my current location - with the special feature that only one of the recursive searches will actually find a solution. The base case is "I am standing on E", and a failure is "I am in a wall" or "I am on a space I have already visited".
Edit: for interest's sake, here is an OO solution (compatible with both Python 2.x and 3.x):
from collections import namedtuple
Dir = namedtuple("Dir", ["char", "dy", "dx"])
class Maze:
START = "S"
END = "E"
WALL = "#"
PATH = " "
OPEN = {PATH, END} # map locations you can move to (not WALL or already explored)
RIGHT = Dir(">", 0, 1)
DOWN = Dir("v", 1, 0)
LEFT = Dir("<", 0, -1)
UP = Dir("^", -1, 0)
DIRS = [RIGHT, DOWN, LEFT, UP]
#classmethod
def load_maze(cls, fname):
with open(fname) as inf:
lines = (line.rstrip("\r\n") for line in inf)
maze = [list(line) for line in lines]
return cls(maze)
def __init__(self, maze):
self.maze = maze
def __str__(self):
return "\n".join(''.join(line) for line in self.maze)
def find_start(self):
for y,line in enumerate(self.maze):
try:
x = line.index("S")
return y, x
except ValueError:
pass
# not found!
raise ValueError("Start location not found")
def solve(self, y, x):
if self.maze[y][x] == Maze.END:
# base case - endpoint has been found
return True
else:
# search recursively in each direction from here
for dir in Maze.DIRS:
ny, nx = y + dir.dy, x + dir.dx
if self.maze[ny][nx] in Maze.OPEN: # can I go this way?
if self.maze[y][x] != Maze.START: # don't overwrite Maze.START
self.maze[y][x] = dir.char # mark direction chosen
if self.solve(ny, nx): # recurse...
return True # solution found!
# no solution found from this location
if self.maze[y][x] != Maze.START: # don't overwrite Maze.START
self.maze[y][x] = Maze.PATH # clear failed search from map
return False
def main():
maze = Maze.load_maze("somemaze.txt")
print("Maze loaded:")
print(maze)
try:
sy, sx = maze.find_start()
print("solving...")
if maze.solve(sy, sx):
print(maze)
else:
print(" no solution found")
except ValueError:
print("No start point found.")
if __name__=="__main__":
main()
and when run produces:
Maze loaded:
####################################
#S# ## ######## # # # # #
# # # # # # #
# # ##### ## ###### # ####### # #
### # ## ## # # # #### #
# # # ####### # ### #E#
####################################
solving...
####################################
#S# ## ######## # #>>>>>v# >>v# #
#v#>>v# >>>v #^# >>>>^#>>v#
#>>^#v#####^##v######^# ####### #v#
### #v##>>>^##>>>>>v#^# # ####v#
# #>>>^# #######>>^# ### #E#
####################################
Note that the assignment as given has a few unPythonic elements:
it asks for camelCase function names rather than underscore_separated
it suggests using a global variable rather than passing data explicitly
it asks for find_start to return flag values on failure rather than raising an exception

Categories

Resources