forked from ryanwilsonperkin/rushhour
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rushhour.py
166 lines (140 loc) · 5.57 KB
/
rushhour.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import sys
from collections import deque
from vehicle import Vehicle
GOAL_VEHICLE = Vehicle('X', 4, 2, 'H')
class RushHour(object):
"""A configuration of a single Rush Hour board."""
def __init__(self, vehicles):
"""Create a new Rush Hour board.
Arguments:
vehicles: a set of Vehicle objects.
"""
self.vehicles = vehicles
def __hash__(self):
return hash(self.__repr__())
def __eq__(self, other):
return self.vehicles == other.vehicles
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
s = '-' * 8 + '\n'
for line in self.get_board():
s += '|{0}|\n'.format(''.join(line))
s += '-' * 8 + '\n'
return s
def get_board(self):
"""Representation of the Rush Hour board as a 2D list of strings"""
board = [[' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ']]
for vehicle in self.vehicles:
x, y = vehicle.x, vehicle.y
if vehicle.orientation == 'H':
for i in range(vehicle.length):
board[y][x+i] = vehicle.id
else:
for i in range(vehicle.length):
board[y+i][x] = vehicle.id
return board
def solved(self):
"""Returns true if the board is in a solved state."""
return GOAL_VEHICLE in self.vehicles
def moves(self):
"""Return iterator of next possible moves."""
board = self.get_board()
for v in self.vehicles:
if v.orientation == 'H':
if v.x - 1 >= 0 and board[v.y][v.x - 1] == ' ':
new_v = Vehicle(v.id, v.x - 1, v.y, v.orientation)
new_vehicles = self.vehicles.copy()
new_vehicles.remove(v)
new_vehicles.add(new_v)
yield RushHour(new_vehicles)
if v.x + v.length <= 5 and board[v.y][v.x + v.length] == ' ':
new_v = Vehicle(v.id, v.x + 1, v.y, v.orientation)
new_vehicles = self.vehicles.copy()
new_vehicles.remove(v)
new_vehicles.add(new_v)
yield RushHour(new_vehicles)
else:
if v.y - 1 >= 0 and board[v.y - 1][v.x] == ' ':
new_v = Vehicle(v.id, v.x, v.y - 1, v.orientation)
new_vehicles = self.vehicles.copy()
new_vehicles.remove(v)
new_vehicles.add(new_v)
yield RushHour(new_vehicles)
if v.y + v.length <= 5 and board[v.y + v.length][v.x] == ' ':
new_v = Vehicle(v.id, v.x, v.y + 1, v.orientation)
new_vehicles = self.vehicles.copy()
new_vehicles.remove(v)
new_vehicles.add(new_v)
yield RushHour(new_vehicles)
def load_file(rushhour_file):
vehicles = []
for line in rushhour_file:
line = line[:-1] if line.endswith('\n') else line
id, x, y, orientation = line
vehicles.append(Vehicle(id, int(x), int(y), orientation))
return RushHour(set(vehicles))
def breadth_first_search(r, max_depth=25):
"""
Find solutions to given RushHour board using breadth first search.
Returns a dictionary with named fields:
visited: the number of configurations visited in the search
solutions: paths to the goal state
depth_states: the number of states visited at each depth
Arguments:
r: A RushHour board.
Keyword Arguments:
max_depth: Maximum depth to traverse in search (default=25)
"""
visited = set()
solutions = list()
depth_states = dict()
queue = deque()
queue.appendleft((r, tuple()))
while len(queue) != 0:
board, path = queue.pop()
new_path = path + tuple([board])
depth_states[len(new_path)] = depth_states.get(len(new_path), 0) + 1
if len(new_path) >= max_depth:
break
if board in visited:
continue
else:
visited.add(board)
if board.solved():
solutions.append(new_path)
else:
queue.extendleft((move, new_path) for move in board.moves())
return {'visited': visited,
'solutions': solutions,
'depth_states': depth_states}
def solution_steps(solution):
"""Generate list of steps from a solution path."""
steps = []
for i in range(len(solution) - 1):
r1, r2 = solution[i], solution[i+1]
v1 = list(r1.vehicles - r2.vehicles)[0]
v2 = list(r2.vehicles - r1.vehicles)[0]
if v1.x < v2.x:
steps.append('{0}R'.format(v1.id))
elif v1.x > v2.x:
steps.append('{0}L'.format(v1.id))
elif v1.y < v2.y:
steps.append('{0}D'.format(v1.id))
elif v1.y > v2.y:
steps.append('{0}U'.format(v1.id))
return steps
if __name__ == '__main__':
filename = sys.argv[1]
with open(filename) as rushhour_file:
rushhour = load_file(rushhour_file)
results = breadth_first_search(rushhour, max_depth=100)
print '{0} Solutions found'.format(len(results['solutions']))
for solution in results['solutions']:
print 'Solution: {0}'.format(', '.join(solution_steps(solution)))
print '{0} Nodes visited'.format(len(results['visited']))