-
Notifications
You must be signed in to change notification settings - Fork 0
/
JamPuzzle.py
218 lines (176 loc) · 6.49 KB
/
JamPuzzle.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
"""This module includes the JamPuzzle class, which represents an instance of the Traffic
Jam Puzzle. It supports all moves, legal or not, and will track the state of the puzzle
for any moves made. It thus assumed the input puzzle is a valid puzzle with a solution.
It also includes the Vehicle class, which represents a single vehicle with a position,
orientation, and vehicle type.
(c) 2016 Gary Chen
"""
from enum import Enum, IntEnum
"""Some useful enums!
VehicleTypes holds vehicle length for each type. Orientations just
tracks orientation of a vehicle, useful for dereferencing position tuples.
"""
class VehicleTypes(IntEnum):
car = 2
truck = 3
class Orientations(IntEnum):
horizontal = 0
vertical = 1
class JamPuzzle:
"""Represents an instance of the game
Attributes:
gridSizeX (int): width of the grid
gridSizeY (int): height of the grid
doorPos (int): position of the door (0 indexed)
vehicles (Vehicle[]]): a list of Vehicle objects representing
the initial state of the game
"""
def __init__(self, gridSizeX, gridSizeY, doorPos, vehicles):
self.gridSizeY = gridSizeY
self.gridSizeX = gridSizeX
self.doorPos = doorPos
self.vehicles = vehicles
def getSizeTuple(self):
"""Returns grid sizes as an (x, y) tuple
Return:
(int, int): tuple representing (width, height) of puzzle grid
"""
return (self.gridSizeX, self.gridSizeY)
def getGrid(self):
"""Returns a 2D char list representation of the state of the puzzle, not
including the exit door's position. Each car is represented by a
letter in all the sapces it occupies; blank spaces are '_'. The goal
car is the last car found that is aligned with the exit door, and is
represented by '#'.
Return:
grid = String[][]: 2D string array graphically depicting puzzle state
"""
symbol = ord('A')
grid = [["_" for y in range(self.gridSizeY)] for x in range(self.gridSizeX)]
for v in self.vehicles:
# iterate through each vehicle, assigning it a symbol and replacing its
# covered locations with that symbol in the grid
tempSymbol = chr(symbol)
if v.pos[0] == self.doorPos and v.orientation == Orientations.vertical:
tempSymbol = '#'
else:
symbol += 1
locs = v.coveredUnits()
for l in locs:
grid[l[0]][l[1]] = tempSymbol
return grid
def move(self, pos, moves):
"""Wrapper for moveVehicle()
Args:
pos ((int, int)): position of vehicle to move (x, y)
moves (int): number of moves to move vehicle
"""
v = self.getVehicleAt(pos)
if v == None:
raise Exception("Can't move vehicle; not found", pos)
self.moveVehicle(v, moves)
def moveVehicle(self, veh, moves):
"""Moves the vehicle by an amount in the directions of its
orientation . Does not check for obstacles.
Args:
veh (Vehicle): the vehicle to be moved
moves (int): amount to move (can be negative)
"""
orient = veh.orientation
newPosList = list(veh.pos)
newPosList[orient] += moves
veh.pos = tuple(newPosList)
def moveRange(self, veh):
"""Finds legal move range of a vehicle in current puzzle.
Args:
veh (Vehicle): the vehicle to check
Return:
(range): a range from min moves to max moves + 1
"""
minMove = 0
# iterate over spaces behind to check
for i in range(-1, -veh.pos[veh.orientation]-1, -1):
# Only way to change a value in a tuple by index :/
newPosList = list(veh.pos)
newPosList[veh.orientation] += i
newPosTuple = tuple(newPosList)
blocked = False
for v in self.vehicles:
if newPosTuple in v.coveredUnits():
blocked = True
break
if blocked:
break
else:
minMove = i
maxMove = 0
# iterate over spaces ahead to check, not pos of vehicle. Accounts for length of vehicle
for j in range(veh.vType, self.getSizeTuple()[veh.orientation]-veh.pos[veh.orientation]):
# j is # of spaces ahead of vehicle position to check!
# not position to check, or # of moves
newPosList = list(veh.pos)
newPosList[veh.orientation]+=j
newPosTuple = tuple(newPosList)
if (not self.getVehicleAt(newPosTuple) == None):
break
else:
maxMove = j - veh.vType + 1
return range(minMove, maxMove+1)
def getVehicleAt(self, pos):
"""Retrieves vehicle at given position in puzzle
Args:
pos ((int, int)): position of vehicle to find
Return:
v (Vehicle): the found vehicle, if it exists
None: if no vehicle was found at the position
"""
for v in self.vehicles:
if pos in v.coveredUnits():
return v
return None
def won(self):
"""Checks if game is in a win-state, i.e. a vertical vehicle is adjacent
to the door on the top edge of the puzzle grid
Return:
(bool): boolean representing if game is in a win state or not
"""
v = self.getVehicleAt((self.doorPos, 0))
if not v == None and v.orientation == Orientations.vertical:
return True
return False
def __str__(self):
result = " " * self.doorPos + "* " + " " * (self.gridSizeX - self.doorPos - 1) + "\n"
grid = self.getGrid()
result += "\n".join([" ".join([grid[x][y] for x in range(self.gridSizeX)]) for y in range(self.gridSizeY)]) + "\n"
return result
def __eq__(self, b):
return self.getGrid() == b.getGrid()
class Vehicle:
"""Represents a single vehicle instance
Attributes:
pos ((int, int)): the (x, y) position of the top-left square of the vehicle
orientation (Orientations): an Orientations enum value representing direction
the vehicle is facing (vertical or horizontal; backwards or forwards doesn't matter)
vType (VehicleTypes): a VehicleTypes enum value representing type of vehicle, either
car or truck. Also represents length of vehicle.
"""
def __init__(self, pos, orientation, vType):
self.pos = pos
self.orientation = orientation
self.vType = vType
def coveredUnits(self):
"""Returns list of all locations covered by this vehicle.
Return:
result ((int, int)[]): Array of tuples representing all coordinates this
vehicle must cover, given its position and length.
"""
if self.orientation == Orientations.vertical:
result = [(self.pos[0], self.pos[1] + i) for i in range(int(self.vType))]
if self.orientation == Orientations.horizontal:
result = [(self.pos[0] + i, self.pos[1]) for i in range(int(self.vType))]
return result
def __str__(self):
orientTxt = "Horizontal" if self.orientation == Orientations.horizontal else "Vertical"
vehTxt = "Car" if self.vType == VehicleTypes.car else "Truck"
positions = str(self.coveredUnits())
return orientTxt + " " + vehTxt + " at (" + str(self.pos[0]) + "," + str(self.pos[1]) + ") covering " + positions