-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
30 changed files
with
3,195 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
from time import sleep | ||
|
||
# Maze.py | ||
# original version by db, Fall 2017 | ||
# Feel free to modify as desired. | ||
|
||
# Maze objects are for loading and displaying mazes, and doing collision checks. | ||
# They are not a good object to use to represent the state of a robot mazeworld search | ||
# problem, since the locations of the walls are fixed and not part of the state; | ||
# you should do something else to represent the state. However, each Mazeworldproblem | ||
# might make use of a (single) maze object, modifying it as needed | ||
# in the process of checking for legal moves. | ||
|
||
# Test code at the bottom of this file shows how to load in and display | ||
# a few maze data files (e.g., "maze1.maz", which you should find in | ||
# this directory.) | ||
|
||
# the order in a tuple is (x, y) starting with zero at the bottom left | ||
|
||
# Maze file format: | ||
# # is a wall | ||
# . is a floor | ||
# the command \robot x y adds a robot at a location. The first robot added | ||
# has index 0, and so forth. | ||
|
||
|
||
class Maze: | ||
|
||
# internal structure: | ||
# self.walls: set of tuples with wall locations | ||
# self.width: number of columns | ||
# self.rows | ||
|
||
def __init__(self, mazefilename): | ||
|
||
self.robotloc = [] | ||
# read the maze file into a list of strings | ||
f = open(mazefilename) | ||
lines = [] | ||
for line in f: | ||
line = line.strip() | ||
# ignore blank lines | ||
if len(line) == 0: | ||
pass | ||
elif line[0] == "\\": | ||
#print("command") | ||
# there's only one command, \robot, so assume it is that | ||
parms = line.split() | ||
x = int(parms[1]) | ||
y = int(parms[2]) | ||
self.robotloc.append(x) | ||
self.robotloc.append(y) | ||
else: | ||
lines.append(line) | ||
f.close() | ||
|
||
# Adding this as the index to represent who's turn it is to move | ||
self.robotloc.append(0) | ||
|
||
self.width = len(lines[0]) | ||
self.height = len(lines) | ||
|
||
self.map = list("".join(lines)) | ||
|
||
def index(self, x, y): | ||
return (self.height - y - 1) * self.width + x | ||
|
||
# returns True if the location is a floor | ||
def is_floor(self, x, y): | ||
if x < 0 or x >= self.width: | ||
return False | ||
if y < 0 or y >= self.height: | ||
return False | ||
|
||
return self.map[self.index(x, y)] == "." | ||
|
||
def has_robot(self, x, y): | ||
if x < 0 or x >= self.width: | ||
return False | ||
if y < 0 or y >= self.height: | ||
return False | ||
|
||
# Needed to add the -1 in order to account for the turn element in the state | ||
for i in range(0, len(self.robotloc) - 1, 2): | ||
rx = self.robotloc[i] | ||
ry = self.robotloc[i + 1] | ||
if rx == x and ry == y: | ||
return True | ||
|
||
return False | ||
|
||
|
||
# function called only by __str__ that takes the map and the | ||
# robot state, and generates a list of characters in order | ||
# that they will need to be printed out in. | ||
def create_render_list(self): | ||
renderlist = list(self.map) | ||
|
||
robot_number = 0 | ||
|
||
# Needed to add the -1 in order to account for the turn element in the state | ||
for index in range(0, len(self.robotloc) - 1, 2): | ||
|
||
x = self.robotloc[index] | ||
y = self.robotloc[index + 1] | ||
|
||
renderlist[self.index(x, y)] = robotchar(robot_number) | ||
robot_number += 1 | ||
|
||
return renderlist | ||
|
||
def __str__(self): | ||
|
||
# render robot locations into the map | ||
renderlist = self.create_render_list() | ||
|
||
# use the renderlist to construct a string, by | ||
# adding newlines appropriately | ||
|
||
s = "" | ||
for y in range(self.height - 1, -1, -1): | ||
for x in range(self.width): | ||
s += renderlist[self.index(x, y)] | ||
|
||
s += "\n" | ||
|
||
return s | ||
|
||
|
||
def robotchar(robot_number): | ||
return chr(ord("A") + robot_number) | ||
|
||
|
||
# Some test code | ||
|
||
if __name__ == "__main__": | ||
test_maze1 = Maze("maze1.maz") | ||
print(test_maze1) | ||
|
||
#test_maze2 = Maze("maze2.maz") | ||
#print(test_maze2) | ||
|
||
test_maze3 = Maze("maze3.maz") | ||
print(test_maze3) | ||
|
||
print(test_maze3) | ||
print(test_maze3.robotloc) | ||
|
||
print(test_maze3.is_floor(2, 3)) | ||
print(test_maze3.is_floor(-1, 3)) | ||
print(test_maze3.is_floor(1, 0)) | ||
|
||
print(test_maze3.has_robot(1, 0)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
from Maze import Maze | ||
from time import sleep | ||
|
||
# Author: Ben Williams '25 | ||
# Date: September 14th, 2023 | ||
|
||
|
||
class MazeworldProblem: | ||
|
||
## you write the constructor, and whatever methods your astar function needs | ||
|
||
def __init__(self, maze, goal_locations): | ||
self.maze = maze | ||
self.start_state = tuple(maze.robotloc) | ||
self.goal_state = goal_locations | ||
# Excludes final index that may represent whose turn it is | ||
self.num_robots = len(maze.robotloc) // 2 | ||
|
||
def __str__(self): | ||
string = "Mazeworld problem: " | ||
return string | ||
|
||
# given a sequence of states (including robot turn), modify the maze and print it out. | ||
# (Be careful, this does modify the maze!) | ||
|
||
def animate_path(self, path): | ||
# reset the robot locations in the maze | ||
self.maze.robotloc = tuple(self.start_state[0:(len(self.start_state) - 1)]) | ||
print("Start state: ", self.start_state) | ||
print("Goal state: ", self.goal_state) | ||
for state in path: | ||
print(str(self)) | ||
self.maze.robotloc = tuple(state[0:(len(self.start_state) - 1)]) | ||
sleep(1) | ||
|
||
print(str(self.maze)) | ||
|
||
# Move robots together or separately? | ||
def get_successors(self, state): | ||
# Update locations on the maze | ||
self.maze.robotloc = list(state) | ||
successor_states = [] | ||
|
||
# Determine which robot is being moved | ||
robot_id = state[len(state) - 1] | ||
|
||
# Go through all possible movements | ||
for x_mov in range(-1, 2): | ||
for y_mov in range(-1, 2): | ||
# Ignore diagonal moves | ||
if abs(x_mov) + abs(y_mov) > 1: | ||
continue | ||
new_state = [] | ||
new_state.extend(state) | ||
new_state[robot_id * 2] += x_mov | ||
new_state[1 + robot_id * 2] += y_mov | ||
new_state[len(new_state) - 1] = (robot_id + 1) % self.num_robots | ||
if self.is_free_space(new_state[robot_id * 2], new_state[1 + robot_id * 2]) or (x_mov == 0 and y_mov == 0): | ||
successor_states.append(tuple(new_state)) | ||
|
||
return successor_states | ||
|
||
# Returns whether a space is free and open on the current maze | ||
def is_free_space(self, x, y): | ||
if self.maze.is_floor(x, y) and not self.maze.has_robot(x, y): | ||
return True | ||
return False | ||
|
||
# Returns whether all the robots are at the goal positions or not | ||
def is_goal_state(self, state): | ||
self.maze.robotloc = list(state) | ||
# We ignore the turn-element index of the state | ||
for i in range(len(state) - 1): | ||
if state[i] != self.goal_state[i]: | ||
return False | ||
return True | ||
|
||
|
||
# A bit of test code. You might want to add to it to verify that things | ||
# work as expected. | ||
if __name__ == "__main__": | ||
test_maze3 = Maze("maze3.maz") | ||
test_mp = MazeworldProblem(test_maze3, (1, 4, 1, 3, 1, 2)) | ||
print(test_mp.maze) | ||
# print(test_mp.get_successors((1, 0, 1, 1, 2, 1, 0))) | ||
# print(test_mp.get_successors((1, 0, 1, 1, 2, 1, 1))) | ||
# print(test_mp.get_successors((1, 0, 1, 1, 2, 1, 2))) | ||
|
||
print(test_mp.get_successors((1, 0, 3, 1, 2, 5, 0))) | ||
# print(test_mp.is_free_space(1, 1)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
class SearchSolution: | ||
def __init__(self, problem, search_method): | ||
self.problem_name = str(problem) | ||
self.search_method = search_method | ||
self.path = [] | ||
self.nodes_visited = 0 | ||
self.cost = 0 | ||
|
||
def __str__(self): | ||
string = "----\n" | ||
string += "{:s}\n" | ||
string += "attempted with search method {:s}\n" | ||
|
||
if len(self.path) > 0: | ||
|
||
string += "number of nodes visited: {:d}\n" | ||
string += "solution length: {:d}\n" | ||
string += "cost: {:d}\n" | ||
string += "path: {:s}\n" | ||
|
||
string = string.format(self.problem_name, self.search_method, | ||
self.nodes_visited, len(self.path), self.cost, str(self.path)) | ||
else: | ||
string += "no solution found after visiting {:d} nodes\n" | ||
string = string.format(self.problem_name, self.search_method, self.nodes_visited) | ||
|
||
return string |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
from Maze import Maze | ||
from time import sleep | ||
|
||
# Author: Ben Williams '25 | ||
# Date: September 22nd, 2023 | ||
|
||
|
||
# A version of Maze world where the robot is blind and has no idea where it starts | ||
class SensorlessProblem: | ||
def __init__(self, maze): | ||
self.maze = maze | ||
# The robot could be anywhere - so we start with all the floor spaces | ||
self.start_state = self.get_open_spaces() | ||
|
||
# Goal state in this case is how many possibilities we want to have left | ||
self.goal_state = 1 | ||
|
||
def get_successors(self, state): | ||
successor_states = [] | ||
|
||
# Loop through each possible action, except this time we do not allow waiting | ||
for x_mov in range(-1, 2): | ||
for y_mov in range(-1, 2): | ||
# Ignore diagonal moves | ||
if abs(x_mov) + abs(y_mov) > 1: | ||
continue | ||
|
||
# Do not allow waiting | ||
if x_mov == y_mov == 0: | ||
continue | ||
|
||
new_state = set() | ||
# Try the movement on all current possible locations | ||
for possible_location in state: | ||
new_x = possible_location[0] + x_mov | ||
new_y = possible_location[1] + y_mov | ||
|
||
# If the floor is open from this possible location, we could end up there | ||
if self.maze.is_floor(new_x, new_y): | ||
new_state.add((new_x, new_y)) | ||
# Otherwise, we hit a wall and stay in the same spot | ||
else: | ||
new_state.add(possible_location) | ||
|
||
successor_states.append(tuple(new_state)) | ||
|
||
return successor_states | ||
|
||
def __str__(self): | ||
string = "Blind robot problem: " | ||
return string | ||
|
||
# In this, the letters represent all possible locations the single robot could be in | ||
# We animate the narrowing-down of positions over time | ||
def animate_path(self, path): | ||
# Convert the start state into a list of locations | ||
start_list_of_tuples = list(self.start_state) | ||
start_possible_positions = [] | ||
for possible_position in start_list_of_tuples: | ||
start_possible_positions.extend(list(possible_position)) | ||
|
||
# Reset locations in the maze | ||
self.maze.robotloc = start_possible_positions | ||
|
||
for state in path: | ||
print(str(self)) | ||
|
||
# Convert the state into a list of locations | ||
list_of_tuples = list(state) | ||
robot_possible_positions = [] | ||
for possible_position in list_of_tuples: | ||
robot_possible_positions.extend(list(possible_position)) | ||
self.maze.robotloc = robot_possible_positions | ||
|
||
sleep(1) | ||
print(str(self.maze)) | ||
|
||
# Returns a list of all floor locations on the maze | ||
def get_open_spaces(self): | ||
open_spaces = [] | ||
# Find every floor location on the maze | ||
for x in range(self.maze.width): | ||
for y in range(self.maze.height): | ||
if self.maze.is_floor(x, y): | ||
open_spaces.append((x, y)) | ||
return tuple(open_spaces) | ||
|
||
# If there is only one possible location, then we know where we are and the problem is solved | ||
def is_goal_state(self, state): | ||
return len(state) == 1 | ||
|
||
# So that we can run the BFS on this | ||
def is_safe_state(self, state): | ||
return True | ||
|
||
|
||
## A bit of test code | ||
if __name__ == "__main__": | ||
test_maze3 = Maze("maze3.maz") | ||
test_problem = SensorlessProblem(test_maze3) |
Oops, something went wrong.