-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRobot.py
356 lines (286 loc) · 12.6 KB
/
Robot.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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
import random
import emoji
import time
import os
import constants
import Node
class Robot:
"""
Heuristically Programmed Algorithmic computer n°9000.
Also known as HAL 9000.
Originally created for Discovery One ship, but finally used
as a domestic hoover. Less risky.
"""
score = 100
cleaned_dust = 0
stored_jewels = 0
lost_jewels = 0
cycles = 0
current_move = 0
available_moves = 10
mansion = None
position = {'x': -1, 'y': -1}
current_cell = ''
targets = []
actions = []
def __init__(self, mansion):
"""Hal's birthplace"""
self.mansion = mansion
self.mansion_dimensions = mansion.get_mansion_dimensions()
self.position['x'] = random.randint(0, self.mansion_dimensions['width'] - 1)
self.position['y'] = random.randint(0, self.mansion_dimensions['height'] - 1)
def live(self):
while True:
self.mansion.populate()
if self.current_move < self.available_moves:
self.current_move += 1
if len(self.actions) is 0 or self.current_move == self.available_moves:
if self.look_for_new_targets():
self.think()
self.current_move = 0
self.act()
# Utilities
self.print_environment()
self.cycles += 1
time.sleep(0.25)
def look_for_new_targets(self):
"""Capteur"""
board = self.mansion.board
new_targets = []
self.modify_score(-2)
for i in range(len(board)):
for j in range(len(board[i])):
if board[i][j] is not constants.EMPTY:
new_targets.append([i, j])
if self.targets != new_targets:
self.targets = new_targets
return new_targets
else:
return None
def think(self):
"""What are the most efficient moves ?"""
def find_move(robot):
start = Node.Node(robot.position['x'], robot.position['y'], robot.get_current_cell())
goals = find_goals(robot)
# Set of all A*'s best paths
path_set = []
for goal in goals:
# Do A* calculation for each goal
path_set.append(a_star(start, goal))
best_path = []
g_score = 0
max_elements = 0
for path in path_set:
elements = path[0]
move = path[1]
# Intelligence is here
if ((elements > max_elements) and (len(move) <= robot.available_moves)) \
or ((elements == max_elements) and (len(move) <= robot.available_moves)
and (move[-1].g_score < g_score)):
best_path = move
max_elements = elements
g_score = move[-1].g_score
return convert_path_to_move(best_path)
def find_goals(robot):
"""Return every potential goals"""
goals = []
for target in robot.targets:
goals.append(Node.Node(target[1], target[0], robot.mansion.board[target[1]][target[0]]))
return goals
def convert_path_to_move(path):
"""Convert a node path to a move path"""
moves = []
prev_x = path[0].x
prev_y = path[0].y
for node in path:
if (node.x == prev_x + 1) and (node.y == prev_y):
prev_x = node.x
moves.append(constants.RIGHT)
elif (node.x == prev_x - 1) and (node.y == prev_y):
prev_x = node.x
moves.append(constants.LEFT)
elif (node.x == prev_x) and (node.y == prev_y + 1):
prev_y = node.y
moves.append(constants.DOWN)
elif (node.x == prev_x) and (node.y == prev_y - 1):
prev_y = node.y
moves.append(constants.UP)
elif (node.x != prev_x) and (node.y != prev_y):
return False
if self.mansion.board[node.y][node.x] is constants.JEWEL:
moves.append(constants.TAKE)
elif self.mansion.board[node.y][node.x] is constants.DUST:
moves.append(constants.CLEAN)
elif self.mansion.board[node.y][node.x] is constants.BOTH:
moves.append(constants.TAKE)
moves.append(constants.CLEAN)
return moves
def a_star(start, goal):
"""A* algorithm"""
# Start node f_score
start.f_score = heuristic_cost_estimate(start, goal)
# The set of node already evaluated
closed_set = []
# The set of currently discovered nodes that are not evaluated yet
open_set = [start]
# While open_set is not empty
while open_set:
# Chose as current the node in open_set having the lowest f_score value
current = best_f_node(open_set)
if current.equals(goal):
return reconstruct_path(current)
open_set.remove(current)
closed_set.append(current)
# Find the neighbor nodes of current
neighbor_set = neighbors_of(current)
for neighbor in neighbor_set:
if neighbor.belongs_to(closed_set): # if neighbor in closed_set:
# Ignore the neighbor which is already evaluated
continue
# Distance from start to the neighbor
tentative_g_score = current.g_score + neighbor.weight
# tentative_g_score = current.g_score + dist_between(current, neighbor)
if not neighbor.belongs_to(open_set): # if neighbor not in open_set:
# Hurray! We discovered a new node
open_set.append(neighbor)
elif tentative_g_score >= neighbor.g_score:
# This is not a better path
continue
# This path is the best!
neighbor.came_from = current
neighbor.g_score = tentative_g_score
neighbor.f_score = neighbor.g_score + heuristic_cost_estimate(neighbor, goal)
# Failure
return False
def reconstruct_path(current):
"""Return the path from the start node to the current node"""
number_of_elements = 0
if current.weight == constants.BOTH_WEIGHT:
number_of_elements = 2
elif (current.weight == constants.JEWEL_WEIGHT) or (current.weight == constants.DUST_WEIGHT):
number_of_elements = 1
total_path = [current]
while current.came_from:
current = current.came_from
total_path = [current] + total_path
if current.weight == constants.BOTH_WEIGHT:
number_of_elements += 2
elif (current.weight == constants.JEWEL_WEIGHT) or (current.weight == constants.DUST_WEIGHT):
number_of_elements += 1
return [number_of_elements, total_path]
def neighbors_of(current):
"""Return the list of neighbor nodes of the current node"""
neighbors = []
mansion_dimensions = self.mansion.get_mansion_dimensions()
current_x = current.x
current_y = current.y
x_min = 0
y_min = 0
x_max = mansion_dimensions['width']-1
y_max = mansion_dimensions['height']-1
if current_x > x_min:
neighbors.append(Node.Node(current_x-1, current_y, self.mansion.board[current_y][current_x-1]))
if current_x < x_max:
neighbors.append(Node.Node(current_x+1, current_y, self.mansion.board[current_y][current_x+1]))
if current_y > y_min:
neighbors.append(Node.Node(current_x, current_y-1, self.mansion.board[current_y-1][current_x]))
if current_y < y_max:
neighbors.append(Node.Node(current_x, current_y+1, self.mansion.board[current_y+1][current_x]))
return neighbors
def best_f_node(node_set):
best_node = node_set[0]
for node in node_set:
if node.f_score < best_node.f_score:
best_node = node
return best_node
def dist_between(current, node):
return abs(current.x - node.x) + abs(current.y - node.y)
def heuristic_cost_estimate(current, node):
return dist_between(current, node) * constants.EMPTY_WEIGHT
actions = find_move(self)
if self.score//10 > 0:
self.available_moves = self.score//10
else:
self.available_moves = 3
self.actions = actions[:self.available_moves]
def act(self):
"""Do what you need to do"""
if self.actions:
self.do_something(self.actions.pop(0))
def do_something(self, action):
"""Déplacement"""
got_something = False
got_wrong = False
did_something = None
current_cell = self.get_current_cell()
if action is constants.UP:
if self.position['y'] > 0:
self.position['y'] -= 1
did_something = True
elif action is constants.DOWN:
if self.position['y'] < self.mansion_dimensions['height'] - 1:
self.position['y'] += 1
did_something = True
elif action is constants.RIGHT:
if self.position['x'] < self.mansion_dimensions['width'] - 1:
self.position['x'] += 1
did_something = True
elif action is constants.LEFT:
if self.position['x'] > 0:
self.position['x'] -= 1
did_something = True
elif action is constants.CLEAN:
if current_cell is constants.DUST:
self.cleaned_dust += 1
got_something = True
elif current_cell is constants.JEWEL or current_cell is constants.BOTH:
self.lost_jewels += 1
got_wrong = True
self.mansion.board[self.position['y']][self.position['x']] = constants.EMPTY
did_something = True
elif action is constants.TAKE:
if current_cell is constants.JEWEL:
self.stored_jewels += 1
got_something = True
self.mansion.board[self.position['y']][self.position['x']] = constants.EMPTY
elif current_cell is constants.BOTH:
self.stored_jewels += 1
got_something = True
self.mansion.board[self.position['y']][self.position['x']] = constants.DUST
did_something = True
if did_something: # Energy
self.modify_score(-1)
if got_wrong:
self.modify_score(-100)
elif got_something:
self.modify_score(+10)
def get_current_cell(self):
return self.mansion.board[self.position['y']][self.position['x']]
def modify_score(self, value):
if self.score + value < 0:
self.score = 0
else:
self.score += value
def print_environment(self):
"""Display the mansion in the terminal"""
board = self.mansion.board
os.system('clear')
print(' ' + emoji.emojize(constants.DUST, use_aliases=True) + ' Dust : ' + str(self.cleaned_dust) + '\n')
print(' ' + emoji.emojize(constants.JEWEL, use_aliases=True) + ' Jewels : ' + str(self.stored_jewels) + '\n')
print(' ' + emoji.emojize(constants.JEWEL, use_aliases=True) +
' Lost jewels : ' + str(self.lost_jewels) + '\n')
print(' ' + emoji.emojize(':arrows_clockwise:', use_aliases=True) + ' Cycles : ' + str(self.cycles) + '\n')
print(' ' + emoji.emojize(':heavy_check_mark:', use_aliases=True) + ' Score : ' + str(self.score) + '\n')
print(' Tours : ' + str(self.current_move) + '/' + str(self.available_moves))
for i in range(len(board)):
line_text = ''
for j in range(len(board[i])):
line_text += " "
if i == self.position['y'] and j == self.position['x']:
line_text += ":sunglasses:"
else:
line_text += board[i][j]
line_text += " "
print(emoji.emojize(line_text, use_aliases=True) + '\n')
print("Upcoming moves : \n")
print(self.actions)