Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
yangliu28
GitHub Repository: yangliu28/swarm_formation_sim
Path: blob/master/line_formation_2.py
104 views
1
# second line formation simulation, using merging method instead of climbing
2
# this program shared the same framework with previous line formation simulation program
3
4
# merging method analysis(compared to climbing):
5
# The line may not be as straight as in first simulation, and it would take time to stretch
6
# the line so that robots are evenly spaced. But merging method has the flexibility to
7
# adjust the formation to walls and obstacles, so there is no need to use double walls to
8
# make sure the initial line segments start in the middle to avoid the line hit the wall.
9
10
# comments for research purpose:
11
# Same competing mechanism is used to ensure only one line is being formed.
12
13
# comments for programming purpose:
14
# Similar four statuses have been designed to serve different stages of the robot:
15
# '0': free, wondering around, not in group, available to form a group or join one
16
# '1': forming an initial line segment, or found a line and trying to merge into it
17
# '1_0': initial an line segment, '0' found another '0' and formed a group
18
# '1_1': join a line, merging into the closest spot
19
# '2': already in a line, dynamically adjust space and straightness
20
# '-1': free, wondering around, no interaction with nearby robots
21
# Only allowed status transitions are:
22
# forward: '-1'->'0', '0'->'1', '1'->'2'
23
# backward: '1'->'-1', '2'->'-1'
24
# The 'key_neighbors' attribute carries more information than in previous line simulation.
25
26
# make the line straight and evenly spaced:
27
# A simple proportional control method was used here. The robots on the line '2' never stops
28
# adjusting, there is no threshold for the distance error. The robot is always heading toward
29
# the calculated target position. And to stablize the tracking, the moving velocity will
30
# decrease proportionally to the distance error.
31
32
import pygame
33
import math, random
34
from line_formation_2_robot import LFRobot
35
from formation_functions import *
36
37
pygame.init()
38
39
# add music as background, just for fun :)
40
pygame.mixer.music.load('audio/the-flight-of-the-bumblebee.ogg')
41
pygame.mixer.music.play(-1) # -1 for playing infinite times
42
pygame.mixer.music.set_volume(0.5) # half volume
43
44
# for display, window origin is at left up corner
45
screen_size = (1200, 1000) # width and height
46
background_color = (0, 0, 0) # black background
47
robot_0_color = (0, 255, 0) # for robot status '0', green
48
robot_1_color = (255, 153, 153) # for robot status '1', pink
49
robot_2_color = (255, 51, 0) # for robot status '2', red
50
robot_n1_color = (0, 51, 204) # for robot status '-1', blue
51
robot_size = 5 # robot modeled as dot, number of pixels in radius
52
53
# set up the simulation window and surface object
54
icon = pygame.image.load('icon_geometry_art.jpg')
55
pygame.display.set_icon(icon)
56
screen = pygame.display.set_mode(screen_size)
57
pygame.display.set_caption('Line Formation 2 Simulation - Merging')
58
59
# for physics, continuous world, origin is at left bottom corner, starting (0, 0),
60
# with x axis pointing right, y axis pointing up.
61
# It's more natural to compute the physics in right hand coordiante system.
62
world_size = (100.0, 100.0 * screen_size[1]/screen_size[0])
63
64
# varialbes to configure the simulation
65
robot_quantity = 30
66
# coefficient to resize the robot distribution, to keep initial positions to center
67
distrib_coef = 0.5
68
const_vel = 3.0 # all robots except '2' are moving at this faster constant speed
69
frame_period = 100 # updating period of the simulation and graphics, in ms
70
comm_range = 5.0 # sensing and communication range, the radius
71
line_space = comm_range * 0.7 # line space, between half of and whole communication range
72
merge_min_dist = line_space * 0.7 # minimum distance between two robots to allow merge
73
space_err = line_space * 0.1 # error to determine the space is good when robot arrives
74
life_incre = 8 # number of seconds a new member adds to a group
75
group_id_upper_limit = 1000 # upper limit of random integer for group id
76
n1_life_lower = 3 # lower limit of life time for status '-1'
77
n1_life_upper = 8 # upper limit of life time for status '-1'
78
# coefficient for calculating velocity of robot '2' on the line for adjusting
79
# the smaller the distance error, the smaller the velocity
80
# tune this parameter to adjust the line more quickly and stably
81
adjust_vel_coef = const_vel/line_space * 2
82
83
# instantiate the robot swarm as list
84
robots = [] # container for all robots, index is its identification
85
for i in range(robot_quantity):
86
# random position, away from the window's edges
87
pos_temp = (((random.random()-0.5)*distrib_coef+0.5) * world_size[0],
88
((random.random()-0.5)*distrib_coef+0.5) * world_size[1])
89
vel_temp = const_vel
90
ori_temp = random.random() * 2*math.pi - math.pi # random in (-pi, pi)
91
object_temp = LFRobot(pos_temp, vel_temp, ori_temp)
92
robots.append(object_temp)
93
# instantiate the group variable as dictionary
94
groups = {}
95
# key is the group id
96
# value is a list
97
# 0.first element: the group size, member includes both '2' and '1'
98
# 1.second element: remaining life time
99
# 2.third element: a list of robots on the line in adjacent order, status '2'
100
# 3.fourth element: a list of robots off the line, not in order, status '1'
101
# 4.fifth element: true or false, being the domianant group
102
# instantiate a distance table for every pair of robots
103
# make sure all data in table is being written when updating
104
dist_table = [[0 for j in range(robot_quantity)] for i in range(robot_quantity)]
105
106
# the loop
107
sim_exit = False # simulation exit flag
108
sim_pause = False # simulation pause flag
109
timer_last = pygame.time.get_ticks() # return number of milliseconds after pygame.init()
110
timer_now = timer_last # initialize it with timer_last
111
while not sim_exit:
112
# exit the program
113
for event in pygame.event.get():
114
if event.type == pygame.QUIT:
115
sim_exit = True # exit with the close window button
116
if event.type == pygame.KEYUP:
117
if event.key == pygame.K_SPACE:
118
sim_pause = not sim_pause # reverse the pause flag
119
if (event.key == pygame.K_ESCAPE) or (event.key == pygame.K_q):
120
sim_exit = True # exit with ESC key or Q key
121
122
# skip the rest of the loop if paused
123
if sim_pause: continue
124
125
# update the physics, control rules and graphics at the same time
126
timer_now = pygame.time.get_ticks()
127
if (timer_now - timer_last) > frame_period:
128
timer_last = timer_now # reset timer
129
# prepare the distance data for every pair of robots
130
for i in range(robot_quantity):
131
for j in range(i+1, robot_quantity):
132
# status of '-1' does not involve in any connection, so skip
133
if (robots[i].status == -1) or (robots[j].status == -1):
134
dist_table[i][j] = -1.0
135
dist_table[j][i] = -1.0
136
continue # skip the rest
137
# it only covers the upper triangle without the diagonal
138
vect_temp = (robots[i].pos[0]-robots[j].pos[0],
139
robots[i].pos[1]-robots[j].pos[1])
140
dist_temp = math.sqrt(vect_temp[0]*vect_temp[0] +
141
vect_temp[1]*vect_temp[1])
142
if dist_temp <= comm_range:
143
# only record distance smaller than communication range
144
dist_table[i][j] = dist_temp
145
dist_table[j][i] = dist_temp
146
else: # ignore the neighbors too far away
147
dist_table[i][j] = -1.0
148
dist_table[j][i] = -1.0
149
# sort the distance in another table, record the index here
150
index_list = [[] for i in range(robot_quantity)] # index of neighbors in range
151
# find all robots with non-zero distance
152
for i in range(robot_quantity):
153
for j in range(robot_quantity):
154
if j == i: continue # skip the self, not necessary
155
if dist_table[i][j] > 0: index_list[i].append(j)
156
# sort the index_list in the order of increasing distance
157
for i in range(robot_quantity):
158
len_temp = len(index_list[i]) # number of neighbors
159
if (len_temp < 2): continue # no need to sort
160
else:
161
# bubble sort
162
for j in range(len_temp-1):
163
for k in range(len_temp-1-j):
164
if (dist_table[i][index_list[i][k]] >
165
dist_table[i][index_list[i][k+1]]):
166
# swap the position of the two index
167
index_temp = index_list[i][k]
168
index_list[i][k] = index_list[i][k+1]
169
index_list[i][k+1] = index_temp
170
# get the status list corresponds to the sorted index_list
171
status_list = [[] for i in range(robot_quantity)]
172
for i in range(robot_quantity):
173
for j in index_list[i]:
174
status_list[i].append(robots[j].status)
175
176
# instantiate the status transition variables, for the status check process
177
# the priority to process them is in the following order
178
s_grab_on = {} # robot '0' grabs on robot '2', becoming '1'
179
# key is id of robot '0', value is id of robot '2'
180
s_init_form = {} # robot '0' initial forms with another '0', becoming '1'
181
# key is id of robot '0' that discovers other robot '0' in range
182
# value is a list of robot '0's that are in range, in order of increasing distance
183
s_form_done = {} # robot '1' finishes initial forming, becoming '2'
184
# key is group id
185
# value is a list of id of robot '1's that have finished initial forming
186
s_merge_done = [] # robot '1' finishes merging, becoming '2'
187
# list of robot '1' that finished merging
188
# s_form_lost = [] # robot '1' gets lost during initial forming
189
# # list of group id for the initial forming robots
190
# s_merge_lost = [] # robot '1' gets lost during merging
191
# # list of robot id for the merging robots
192
s_line_lost = [] # robot '2' gets lost during adjusting pos
193
# list of group id for the robots get lost in that group
194
s_group_exp = [] # life time of a group naturally expires
195
# life of group id
196
s_disassemble = [] # disassemble triggerred by robot '1' or '2'
197
# list of lists of group id to be compared for disassembling
198
s_back_0 = [] # robot '-1' gets back to '0'
199
# list of robot id
200
201
# check 'robots' for any status change, schedule them for processing in next step
202
for i in range(robot_quantity):
203
# for the host robot having status of '0'
204
if robots[i].status == 0:
205
# check if this robot has valid neighbors at all
206
if len(index_list[i]) == 0: continue # skip the neighbor check
207
# process neighbors with status '2', highest priority
208
if 2 in status_list[i]:
209
# check the group attribution of all the '2'
210
# get the robot id and group id of the first '2'
211
current_index = status_list[i].index(2)
212
current_robot = index_list[i][current_index]
213
current_group = robots[current_robot].group_id
214
groups_temp = {current_group: [current_robot]}
215
# check if there is still '2' in the list
216
while 2 in status_list[i][current_index+1:]:
217
# indexing the next '2' from current_index+1
218
# update the current_index, current_robot, current_group
219
current_index = status_list[i].index(2, current_index+1)
220
current_robot = index_list[i][current_index]
221
current_group = robots[current_robot].group_id
222
# update groups_temp
223
if current_group in groups_temp.keys():
224
# append this robot in the existing group
225
groups_temp[current_group].append(current_robot)
226
else:
227
# add new group id in groups_temp and add this robot
228
groups_temp[current_group] = [current_robot]
229
# check if there are multiple groups detected from the '2'
230
target_robot = 0 # the target robot to grab on
231
if len(groups_temp.keys()) == 1:
232
# there is only one group detected
233
dist_min = 2*comm_range # smallest distance, start with large one
234
robot_min = -1 # corresponding robot with min distance
235
# search the closest '2'
236
for j in groups_temp.values()[0]:
237
if dist_table[i][j] < dist_min:
238
if ((robots[j].status_2_avail1[0] & robots[j].status_2_avail2[0]) |
239
(robots[j].status_2_avail1[1] & robots[j].status_2_avail2[1])):
240
# check both avail1 and avail2 for both side
241
# there is at least one side is available to be merged
242
dist_min = dist_table[i][j]
243
robot_min = j
244
target_robot = robot_min
245
else:
246
# there is more than one group detected
247
# it is designed that no disassembling trigger from robot '0'
248
# compare which group has the most members in it
249
member_max = 0 # start with 0 number of members in group
250
group_max = 0 # corresponding group id with most members
251
for j in groups_temp.keys():
252
## 'line_formation_1.py' did wrong in the following line
253
# didn't fix it, not serious problem, program rarely goes here
254
if groups[j][0] > member_max:
255
member_max = groups[j][0]
256
group_max = j
257
# search the closest '2' inside that group
258
dist_min = 2*comm_range
259
robot_min = -1
260
for j in groups_temp[group_max]:
261
if dist_table[i][j] < dist_min:
262
if ((robots[j].status_2_avail1[0] & robots[j].status_2_avail2[0]) |
263
(robots[j].status_2_avail1[1] & robots[j].status_2_avail2[1])):
264
dist_min = dist_table[i][j]
265
robot_min = j
266
target_robot = robot_min
267
# check if target robot has been located, prepare to grab on it
268
if target_robot != -1: # not the initial value of robot_min
269
# the target robot should have at least one spot availbe to be merged
270
s_grab_on[i] = target_robot
271
# process neighbors with status '1', second priority
272
elif 1 in status_list[i]:
273
# find the closest '1' and get bounced away by it
274
# still no trigger for disassembling if multiple groups exist in the '1's
275
dist_min = 2*comm_range
276
robot_min = -1
277
for j in range(len(status_list[i])):
278
if status_list[i][j] != 1: continue
279
if dist_table[i][index_list[i][j]] < dist_min:
280
dist_min = dist_table[i][index_list[i][j]]
281
robot_min = index_list[i][j]
282
# target robot located, the robot_min, should not still be -1 here
283
# get bounced away from this robot, update the moving direction
284
vect_temp = (robots[i].pos[0] - robots[robot_min].pos[0],
285
robots[i].pos[1] - robots[robot_min].pos[1])
286
# orientation is pointing from robot_min to host
287
robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])
288
# process neighbors with status '0', least priority
289
else:
290
# establish a list of all '0', in order of increasing distance
291
# to be checked later if grouping is possible and no conflict
292
# this list should be only '0's, already sorted
293
target_list = index_list[i][:]
294
# status transition scheduled, '0' forming initial group with '0'
295
s_init_form[i] = target_list[:]
296
# for the host robot having status of '1'
297
elif robots[i].status == 1:
298
# status of '1' needs to be checked and maintained constantly
299
# 1.check if the important group neighbors are still in range (skiped)
300
# one of key neighbors of robot '1' may appears later when merging,
301
# and key_neighbor variable may has -1 value representing empty
302
# 2.disassemble check, get group attribution of all '1' and '2'
303
status_list_temp = status_list[i][:]
304
index_list_temp = index_list[i][:]
305
# pop out the '0' first
306
while 0 in status_list_temp:
307
index_temp = status_list_temp.index(0)
308
status_list_temp.pop(index_temp)
309
index_list_temp.pop(index_temp)
310
if len(index_list_temp) > 0: # ensure at least one in-group robot around
311
# start the group attribution dictionary with first robot
312
groups_temp = {robots[index_list_temp[0]].group_id: [index_list_temp[0]]}
313
for j in index_list_temp[1:]: # iterating from the second one
314
current_group = robots[j].group_id
315
if current_group in groups_temp.keys():
316
# append this robot in same group
317
groups_temp[current_group].append(j)
318
else:
319
# add new key in the groups_temp dictionary
320
groups_temp[current_group] = [j]
321
# check if there are multiple groups detected
322
if len(groups_temp.keys()) > 1:
323
# status transition scheduled, to disassemble groups
324
s_disassemble.append(groups_temp.keys())
325
# may produce duplicates in s_disassemble, not serious problem
326
# 3.check if any status transition needs to be done
327
if robots[i].status_1_sub == 0:
328
# host robot is in the initial forming phase
329
# check if the neighbor robot is in appropriate distance
330
if abs(dist_table[i][robots[i].key_neighbors[0]] -
331
line_space) < space_err:
332
# status transition scheduled, finish initial forming, '1' to '2'
333
g_it = robots[i].group_id
334
if g_it in s_form_done.keys():
335
s_form_done[g_it].append(i)
336
else:
337
s_form_done[g_it] = [i]
338
elif robots[i].status_1_sub == 1:
339
# host robot is in the merging phase
340
# check if the merging robot reaches the destination
341
vect_temp = (robots[i].status_1_1_des[0] - robots[i].pos[0],
342
robots[i].status_1_1_des[1] - robots[i].pos[1])
343
dist_temp = math.sqrt(vect_temp[0]*vect_temp[0] +
344
vect_temp[1]*vect_temp[1])
345
if dist_temp < space_err:
346
# status transition scheduled, finish merging, '1' to '2'
347
s_merge_done.append(i)
348
# for the host robot having status of '2'
349
elif robots[i].status == 2:
350
# check if all key neighbors are still in range
351
neighbors_secured = True
352
for j in robots[i].key_neighbors:
353
if j == -1: continue # robots at two ends have only one key neighbor
354
if j not in index_list[i]:
355
neighbors_secured = False
356
break
357
if neighbors_secured == False:
358
# status transition scheduled, robot '2' gets lost, disassemble the group
359
s_line_lost.append(robots[i].group_id)
360
else:
361
# all the key neighbors are in goood position
362
# disassemble check, for all the '1' and '2'
363
status_list_temp = status_list[i][:]
364
index_list_temp = index_list[i][:]
365
# pop out the '0' first
366
while 0 in status_list_temp:
367
index_temp = status_list_temp.index(0)
368
status_list_temp.pop(index_temp)
369
index_list_temp.pop(index_temp)
370
# start the group attribution dictionary with first robot
371
if len(index_list_temp) > 0:
372
groups_temp = {robots[index_list_temp[0]].group_id: [index_list_temp[0]]}
373
for j in index_list_temp[1:]:
374
current_group = robots[j].group_id
375
if current_group in groups_temp.keys():
376
groups_temp[current_group].append(j)
377
else:
378
groups_temp[current_group] = [j]
379
# check if there are multiple groups detected
380
if len(groups_temp.keys()) > 1:
381
# status transition scheduled, to disassemble groups
382
s_disassemble.append(groups_temp.keys())
383
# for the host robot having status of '-1'
384
elif robots[i].status == -1:
385
# check if life time expires, and get status back to '0'
386
if robots[i].status_n1_life < 0:
387
s_back_0.append(i)
388
389
# check 'groups' for any status change
390
for g_it in groups.keys():
391
if groups[g_it][4]: continue # already becoming dominant
392
if groups[g_it][0] > robot_quantity/2:
393
# the group has more than half the totoal number of robots
394
groups[g_it][4] = True # becoming dominant
395
groups[g_it][1] = 100.0 # a large number
396
if groups[g_it][1] < 0: # life time of a group expires
397
# schedule operation to disassemble this group
398
s_group_exp.append(g_it)
399
400
# process the scheduled status change, in the order of the priority
401
# 1.s_grab_on, robot '0' grabs on robot '2', becoming '1'
402
# (this part of code is kind of redundant, needs to make it nicer)
403
for i in s_grab_on.keys():
404
it0 = s_grab_on[i] # 'it0' is the robot that robot 'i' tries to grab on
405
# discuss the merging availability of robot 'it0'
406
g_it = robots[it0].group_id # group id of 'it0'
407
# it0 was available when added to s_grab_on, but check again if occupied by others
408
# merge availability of smaller index side
409
side0_avail = robots[it0].status_2_avail1[0] & robots[it0].status_2_avail2[0]
410
side0_des = [-1,-1] # destination if merging at small index end
411
side0_hang = False # indicate if destination at side 0 is hanging
412
side0_next = -1 # next key neighbor expected to meet at this side
413
if side0_avail:
414
# calculate the merging destination
415
# if this side is beyond two ends of the line, it can only be the small end
416
if robots[it0].status_2_sequence == 0:
417
# the grab on robot is the robot of index 0 on the line
418
# get its only neighbor to calculate the line direction
419
it1 = robots[it0].key_neighbors[1] # second robot on the line
420
vect_temp = (robots[it0].pos[0] - robots[it1].pos[0],
421
robots[it0].pos[1] - robots[it1].pos[1]) # from it1 to it0
422
ori_temp = math.atan2(vect_temp[1], vect_temp[0])
423
side0_des = [robots[it0].pos[0] + line_space*math.cos(ori_temp),
424
robots[it0].pos[1] + line_space*math.sin(ori_temp)]
425
side0_hang = True
426
else:
427
# the grab on robot is not at the start of the line
428
# get its smaller index neighbor
429
it1 = robots[it0].key_neighbors[0]
430
side0_next = it1
431
# the destination is the middle position of it0 and it1
432
side0_des = [(robots[it0].pos[0]+robots[it1].pos[0])/2,
433
(robots[it0].pos[1]+robots[it1].pos[1])/2]
434
# merge availability of larger index side
435
side1_avail = robots[it0].status_2_avail1[1] & robots[it0].status_2_avail2[1]
436
side1_des = [-1,-1]
437
side1_hang = False # indicate if destination at side 1 is hanging
438
side1_next = -1 # next key neighbor expected to meet at this side
439
if side1_avail:
440
# calculate the merging destination
441
# if this side is beyond two ends of the line, it can only the large end
442
if robots[it0].status_2_end:
443
# the grab on robot is at the larger index end
444
# get its only neighbor to calculate the line direction
445
it1 = robots[it0].key_neighbors[0] # the inverse second on the line
446
vect_temp = (robots[it0].pos[0] - robots[it1].pos[0],
447
robots[it0].pos[1] - robots[it1].pos[1]) # from it1 to it0
448
ori_temp = math.atan2(vect_temp[1], vect_temp[0])
449
side1_des = [robots[it0].pos[0] + line_space*math.cos(ori_temp),
450
robots[it0].pos[1] + line_space*math.sin(ori_temp)]
451
side1_hang = True
452
else:
453
# the grab on robot is not at the larger index end
454
# get its larger index neighbor
455
it1 = robots[it0].key_neighbors[1]
456
side1_next = it1
457
# the destination is the middle position of it0 and it1
458
side1_des = [(robots[it0].pos[0]+robots[it1].pos[0])/2,
459
(robots[it0].pos[1]+robots[it1].pos[1])/2]
460
# check if there is at least one side is available
461
if side0_avail or side1_avail:
462
# perform operations regardless of which side to merge
463
robots[i].status = 1 # status becoming '1'
464
robots[i].group_id = g_it # assign the group id
465
robots[i].status_1_sub = 1
466
robots[i].key_neighbors = [-1, -1] # initialize with '-1's
467
groups[g_it][0] = groups[g_it][0] + 1 # increase the group size by 1
468
groups[g_it][1] = groups[g_it][1] + life_incre # increase life time
469
groups[g_it][3].append(i)
470
# deciding which side to merge
471
side_decision = -1 # 0 for left, 1 for right
472
if side0_avail and side1_avail:
473
# both sides are available, calculate the distance and compare
474
vect_temp = (side0_des[0]-robots[i].pos[0], side0_des[1]-robots[i].pos[1])
475
side0_dist = math.sqrt(vect_temp[0]*vect_temp[0] +
476
vect_temp[1]*vect_temp[1])
477
vect_temp = (side1_des[0]-robots[i].pos[0], side1_des[1]-robots[i].pos[1])
478
side1_dist = math.sqrt(vect_temp[0]*vect_temp[0] +
479
vect_temp[1]*vect_temp[1])
480
if side0_dist < side1_dist:
481
side_decision = 0
482
else:
483
side_decision = 1
484
elif side0_avail and (not side1_avail):
485
# only small index side is available, choose this side
486
side_decision = 0
487
elif side1_avail and (not side0_avail):
488
# only large index side is available, choose this side
489
side_decision = 1
490
# perform operations according the side decision
491
if side_decision == 0:
492
# operations of taking small index side
493
robots[i].key_neighbors[1] = it0 # has key neighbor at large index side
494
robots[i].status_1_1_des = side0_des
495
vect_temp = (side0_des[0]-robots[i].pos[0], side0_des[1]-robots[i].pos[1])
496
robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])
497
robots[it0].status_2_avail2[0] = False # reverse flag for small side
498
# robot 'it0' does not take 'i' as key nieghbor, only robots on the line
499
if not side0_hang:
500
it1 = side0_next
501
robots[i].key_neighbors[0] = it1
502
robots[it1].status_2_avail2[1] = False
503
else:
504
# operations of taking large index side
505
robots[i].key_neighbors[0] = it0 # has key neighbor at small index side
506
robots[i].status_1_1_des = side1_des
507
vect_temp = (side1_des[0]-robots[i].pos[0], side1_des[1]-robots[i].pos[1])
508
robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])
509
robots[it0].status_2_avail2[1] = False # reverse flag for large side
510
if not side1_hang:
511
it1 = side1_next
512
robots[i].key_neighbors[1] = it1
513
robots[it1].status_2_avail2[0] = False
514
# 2.s_init_form, robot '0' initial forms with another '0', becoming '1'
515
s_pair = [] # the container for finalized initial forming pairs
516
while len(s_init_form.keys()) != 0: # there are still robots to be processed
517
for i in s_init_form.keys():
518
it = s_init_form[i][0] # index temp
519
if it in s_init_form.keys():
520
if s_init_form[it][0] == i: # robot 'it' also recognizes 'i' as closest
521
s_pair.append([i, it])
522
s_init_form.pop(i) # pop out both 'i' and 'it'
523
s_init_form.pop(it)
524
break
525
elif i not in s_init_form[it]:
526
# usually should not be here unless robots have different sensing range
527
s_init_form[i].remove(it)
528
if len(s_init_form[i]) == 0:
529
s_init_form.pop(i)
530
break
531
# have not considered the situation that 'i' in 'it' but not first one
532
else:
533
# will be here if robot 'it' chooses to be bounced away by '1', or grab on '2'
534
s_init_form[i].remove(it)
535
if len(s_init_form[i]) == 0:
536
s_init_form.pop(i)
537
break
538
# process the finalized pairs
539
for pair in s_pair:
540
it0 = pair[0] # get indices of the pair
541
it1 = pair[1]
542
g_it = random.randint(0, group_id_upper_limit)
543
while g_it in groups.keys(): # keep generate until not duplicate
544
g_it = random.randint(0, group_id_upper_limit)
545
# update the 'robots' variable
546
robots[it0].status = 1
547
robots[it1].status = 1
548
robots[it0].group_id = g_it
549
robots[it1].group_id = g_it
550
robots[it0].status_1_sub = 0 # sub status '0' for initial forming
551
robots[it1].status_1_sub = 0
552
robots[it0].key_neighbors = [it1] # name the other as the key neighbor
553
robots[it1].key_neighbors = [it0]
554
# update the 'groups' variable
555
groups[g_it] = [2, 2*life_incre, [], [it0, it1], False] # add new entry
556
# deciding moving direction for the initial forming robots
557
vect_temp = (robots[it1].pos[0]-robots[it0].pos[0],
558
robots[it1].pos[1]-robots[it0].pos[1]) # pointing from it0 to it1
559
ori_temp = math.atan2(vect_temp[1], vect_temp[0])
560
if dist_table[it0][it1] > line_space: # equally dist_table[it1][it0]
561
# attracting each other, most likely this happens
562
robots[it0].ori = ori_temp
563
robots[it1].ori = reset_radian(ori_temp + math.pi)
564
else:
565
# repelling each other
566
robots[it0].ori = reset_radian(ori_temp + math.pi)
567
robots[it1].ori = ori_temp
568
# no need to check if they are already within the space error of line space
569
# this will be done in the next round of status change check
570
# 3.s_form_done, robot '1' finishes initial forming, becoming '2'
571
for g_it in s_form_done.keys():
572
if len(s_form_done[g_it]) == 2: # double check, both robots agree forming is done
573
it0 = s_form_done[g_it][0]
574
it1 = s_form_done[g_it][1]
575
# update 'robots' variable for 'it0' and 'it1'
576
robots[it0].status = 2
577
robots[it1].status = 2
578
# randomly deciding which is begining and end of the line, random choice
579
if random.random() > 0.5: # half chance
580
# swap 'ito' and 'it1', 'it0' as begining, 'it1' as end
581
temp = it0
582
it0 = it1
583
it1 = temp
584
# update the 'robots' variable
585
robots[it0].vel = 0 # give 0 speed temporarily
586
robots[it1].vel = 0
587
robots[it0].status_2_sequence = 0
588
robots[it1].status_2_sequence = 1
589
robots[it0].status_2_end = False
590
robots[it1].status_2_end = True
591
robots[it0].status_2_avail2 = [True, True] # both sides are available
592
robots[it1].status_2_avail2 = [True, True]
593
robots[it0].key_neighbors = [-1, it1] # one side has not key neighbor
594
robots[it1].key_neighbors = [it0, -1]
595
# update the 'groups' variable
596
g_it = robots[it0].group_id
597
groups[g_it][2] = [it0, it1] # add the robots for the line
598
groups[g_it][3] = [] # empty the forming & merging pool
599
# 4.s_merge_done, robot '1' finishes merging, becoming '2'
600
for i in s_merge_done:
601
# finding which side to merge
602
merge_check = -1
603
# '0' for merging at small index end of the line
604
# '1' for merging between two neighbors
605
# '2' for merging at large index end of the line
606
if robots[i].key_neighbors[0] == -1:
607
# only small index neighbor is empty
608
# large index side neighbor is at small index end of the line
609
merge_check = 0
610
else:
611
if robots[i].key_neighbors[1] == -1:
612
# only large index neighbor is empty
613
# small index side neighbor is at large index end of the line
614
merge_check = 2
615
else:
616
# both neighbors are present, merge in between
617
merge_check = 1
618
# perform the merge operations
619
robots[i].status = 2
620
robots[i].vel = 0 # give 0 speed temporarily
621
robots[i].status_2_avail2 = [True, True] # both sides have no merging robot
622
# no need to change key neighbors of robot 'i'
623
g_it = robots[i].group_id
624
groups[g_it][3].remove(i) # remove from the merging pool
625
if merge_check == 0: # merge into small index end of the line
626
it0 = robots[i].key_neighbors[1] # the old small index end
627
robots[i].status_2_sequence = 0
628
robots[i].status_2_end = False
629
groups[g_it][2].insert(0, i) # insert robot 'i' as first one
630
# shift sequence of robots starting from the second one
631
for j in groups[g_it][2][1:]:
632
robots[j].status_2_sequence = robots[j].status_2_sequence + 1
633
# update attributes of the second robot on the line
634
robots[it0].status_2_avail2[0] = True # becomes available again
635
robots[it0].key_neighbors[0] = i # add key neighbor at small index side
636
elif merge_check == 2: # merge into large index end of the line
637
it0 = robots[i].key_neighbors[0] # the old large index end
638
robots[i].status_2_sequence = robots[it0].status_2_sequence + 1
639
# no need to shift sequence of other robots on the line
640
robots[i].status_2_end = True
641
robots[it0].status_2_end = False # no longer the end of the line
642
robots[it0].status_2_avail2[1] = True
643
robots[it0].key_neighbors[1] = i # add key neighbor at large index side
644
groups[g_it][2].append(i) # append at the end of the list
645
elif merge_check == 1: # merge in the middle of two robots
646
# it0 is in small sequence, it1 is in large sequence
647
it0 = robots[i].key_neighbors[0]
648
it1 = robots[i].key_neighbors[1]
649
seq_new = robots[it1].status_2_sequence # 'i' will replace seq of 'it1'
650
robots[i].status_2_sequence = seq_new
651
robots[i].status_2_end = False
652
groups[g_it][2].insert(seq_new, i)
653
# shift sequence of robots starting form 'it1'
654
for j in groups[g_it][2][seq_new+1:]:
655
robots[j].status_2_sequence = robots[j].status_2_sequence + 1
656
# update attribution of it0 and it1
657
robots[it0].status_2_avail2[1] = True # large side of small index robot
658
robots[it0].key_neighbors[1] = i # replace with new member
659
robots[it1].status_2_avail2[0] = True # similar for large index robot
660
robots[it1].key_neighbors[0] = i
661
# # 5.s_form_lost, robot '1' gets lost during initial forming
662
# for g_it in s_form_lost:
663
# # disassemble the group together in s_disassemble
664
# s_disassemble.append[g_it]
665
# # 6.s_merge_lost, robot '1' gets lost during merging, becoming '-1'
666
# for i in s_merge_lost:
667
# robots[i].status = -1
668
# robots[i].vel = const_vel # restore the faster speed
669
# robots[i].ori = random.random() * 2*math.pi - math.pi
670
# robots[i].status_n1_life = random.randint(n1_life_lower, n1_life_upper)
671
# # update the key neighbors' status
672
# if robots[i].key_neighbors[0] != -1:
673
# # robot 'i' has a small index side key neighbor
674
# it0 = robots[i].key_neighbors[0]
675
# robots[it0].status_2_avail2[1] = True
676
# if robots[i].key_neighbors[1] != -1:
677
# # robot 'i' has a large index side key neighbor
678
# it1 = robots[i].key_neighbors[1]
679
# robots[it1].status_2_avail2[0] = True
680
# g_it = robots[i].group_id
681
# groups[g_it][0] = groups[g_it][0] - 1 # decrease group size by 1
682
# # do not decrease group's life time due to member lost
683
# groups[g_it][3].remove(i)
684
# 7.s_line_lost, robot '2' gets lost during adjusting on the line
685
for g_it in s_line_lost:
686
# disassemble the group together in s_disassemble
687
s_disassemble.append[g_it]
688
# 8.s_group_exp, natural life expiration of the groups
689
for g_it in s_group_exp:
690
s_disassemble.append([g_it]) # leave the work to s_disassemble
691
# 9.s_disassemble, mostly triggered by robot '1' or '2'
692
s_dis_list = [] # list of groups that have been finalized for disassembling
693
# compare number of members to decide which groups to disassemble
694
for gs_it in s_disassemble:
695
if len(gs_it) == 1:
696
# disassemble trigger from other sources
697
if gs_it[0] not in s_dis_list:
698
s_dis_list.append(gs_it[0])
699
else:
700
# compare which group has the most members, and disassemble the rest
701
g_temp = gs_it[:]
702
member_max = 0 # number of members in the group
703
group_max = -1 # corresponding group id with most members
704
for g_it in g_temp:
705
if groups[g_it][0] > member_max:
706
member_max = groups[g_it][0]
707
group_max = g_it
708
g_temp.remove(group_max) # remove the group with the most members
709
for g_it in g_temp:
710
if g_it not in s_dis_list:
711
s_dis_list.append(g_it)
712
# start disassembling
713
for g_it in s_dis_list:
714
# update the 'robots' variable
715
for i in groups[g_it][3]: # for robots off the line
716
robots[i].vel = const_vel # restore the faster speed
717
robots[i].status = -1
718
robots[i].status_n1_life = random.randint(n1_life_lower, n1_life_upper)
719
robots[i].ori = random.random() * 2*math.pi - math.pi
720
# same way of exploding the robots on the line
721
num_online = len(groups[g_it][2]) # number of robots on the line
722
num_1_half = num_online/2 # number of robots for the first half
723
num_2_half = num_online - num_1_half # for the second half
724
for i in groups[g_it][2]: # for robots on the line
725
robots[i].vel = const_vel # restore the faster speed
726
robots[i].status = -1
727
robots[i].status_n1_life = random.randint(n1_life_lower, n1_life_upper)
728
ori_temp = 0 # orientation of the line, calculate individually
729
seq_temp = robots[i].status_2_sequence # sequence on the line
730
if robots[i].status_2_sequence == 0:
731
# then get second one on the line
732
it0 = groups[robots[i].group_id][2][1]
733
# orientation points to the small index end
734
ori_temp = math.atan2(robots[i].pos[1]-robots[it0].pos[1],
735
robots[i].pos[0]-robots[it0].pos[0])
736
elif robots[i].status_2_end == True:
737
# then get inverse second one on the line
738
it0 = groups[robots[i].group_id][2][-2]
739
# orientation points to the large index end
740
ori_temp = math.atan2(robots[i].pos[1]-robots[it0].pos[1],
741
robots[i].pos[0]-robots[it0].pos[0])
742
else:
743
# get both neighbors
744
it0 = groups[g_it][2][seq_temp - 1]
745
it1 = groups[g_it][2][seq_temp + 1]
746
if seq_temp < num_1_half: # robot in the first half
747
# orientation points to the small index end
748
ori_temp = math.atan2(robots[it0].pos[1]-robots[it1].pos[1],
749
robots[it0].pos[0]-robots[it1].pos[0])
750
else:
751
# orientation points to the large index end
752
ori_temp = math.atan2(robots[it1].pos[1]-robots[it0].pos[1],
753
robots[it1].pos[0]-robots[it0].pos[0])
754
# then calculate the 'explosion' direction
755
if seq_temp < num_1_half:
756
robots[i].ori = reset_radian(ori_temp + math.pi*(seq_temp+1)/(num_1_half+1))
757
# always 'explode' to the left side
758
else:
759
robots[i].ori = reset_radian(ori_temp + math.pi*(num_online-seq_temp)/(num_2_half+1))
760
# pop out this group from 'groups'
761
groups.pop(g_it)
762
# 10.s_back_0, life time of robot '-1' expires, becoming '0'
763
for i in s_back_0:
764
# still maintaining the old moving direction and velocity
765
robots[i].status = 0
766
767
# update the physics(pos, vel and ori), and wall bouncing, life decrese of '-1'
768
for i in range(robot_quantity):
769
# check if out of boundaries, same algorithm from previous line formation program
770
# change only direction of velocity
771
if robots[i].pos[0] >= world_size[0]: # out of right boundary
772
if math.cos(robots[i].ori) > 0: # velocity on x is pointing right
773
robots[i].ori = reset_radian(2*(math.pi/2) - robots[i].ori)
774
elif robots[i].pos[0] <= 0: # out of left boundary
775
if math.cos(robots[i].ori) < 0: # velocity on x is pointing left
776
robots[i].ori = reset_radian(2*(math.pi/2) - robots[i].ori)
777
if robots[i].pos[1] >= world_size[1]: # out of top boundary
778
if math.sin(robots[i].ori) > 0: # velocity on y is pointing up
779
robots[i].ori = reset_radian(2*(0) - robots[i].ori)
780
elif robots[i].pos[1] <= 0: # out of bottom boundary
781
if math.sin(robots[i].ori) < 0: # velocity on y is pointing down
782
robots[i].ori = reset_radian(2*(0) - robots[i].ori)
783
# update one step of distance
784
travel_dist = robots[i].vel * frame_period/1000.0
785
robots[i].pos[0] = robots[i].pos[0] + travel_dist*math.cos(robots[i].ori)
786
robots[i].pos[1] = robots[i].pos[1] + travel_dist*math.sin(robots[i].ori)
787
# update moving direciton and destination of robot '1'
788
if robots[i].status == 1:
789
if robots[i].status_1_sub == 0: # for the initial forming robots
790
it0 = robots[i].key_neighbors[0]
791
vect_temp = (robots[it0].pos[0]-robots[i].pos[0],
792
robots[it0].pos[1]-robots[i].pos[1])
793
ori_temp = math.atan2(vect_temp[1], vect_temp[0])
794
if dist_table[i][it0] > line_space:
795
robots[i].ori = ori_temp
796
else:
797
robots[i].ori = reset_radian(ori_temp + math.pi)
798
else: # for the merging robot
799
# the merging des should be updated, because robots '2' are also moving
800
if (robots[i].key_neighbors[0] == -1 or
801
robots[i].key_neighbors[1] == -1):
802
# robot 'i' is merging at starting or end of the line
803
it0 = 0 # represent the robot at the end
804
it1 = 0 # represent the robot inward of it0
805
if robots[i].key_neighbors[0] == -1:
806
it0 = robots[i].key_neighbors[1]
807
it1 = robots[it0].key_neighbors[1]
808
else:
809
it0 = robots[i].key_neighbors[0]
810
it1 = robots[it0].key_neighbors[0]
811
# calculate the new destination and new orientation to the destination
812
vect_temp = (robots[it0].pos[0]-robots[it1].pos[0],
813
robots[it0].pos[1]-robots[it1].pos[1]) # from it1 to it0
814
ori_temp = math.atan2(vect_temp[1], vect_temp[0])
815
des_new = [robots[it0].pos[0] + line_space*math.cos(ori_temp),
816
robots[it0].pos[1] + line_space*math.sin(ori_temp)]
817
# update the new destination
818
robots[i].status_1_1_des = des_new[:]
819
vect_temp = (des_new[0]-robots[i].pos[0],
820
des_new[1]-robots[i].pos[1]) # from robot 'i' to des
821
# update the new orientation
822
robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])
823
else:
824
# robot 'i' is merging in between
825
it0 = robots[i].key_neighbors[0]
826
it1 = robots[i].key_neighbors[1]
827
des_new = [(robots[it0].pos[0]+robots[it1].pos[0])/2,
828
(robots[it0].pos[1]+robots[it1].pos[1])/2]
829
robots[i].status_1_1_des = des_new[:]
830
vect_temp = (des_new[0]-robots[i].pos[0],
831
des_new[1]-robots[i].pos[1]) # from robot 'i' to des
832
# update the new orientation
833
robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])
834
# update moving direction, velocity and first merge availability of robot '2'
835
elif robots[i].status == 2:
836
if robots[i].status_2_sequence == 0:
837
# robot 'i' is at the small index end
838
it1 = robots[i].key_neighbors[1] # use it1 because on the large side
839
# update the first merge availability
840
if dist_table[i][it1] > merge_min_dist:
841
robots[i].status_2_avail1[1] = True
842
else:
843
robots[i].status_2_avail1[1] = False
844
# update the moving direction and velocity
845
# for adjusting on the line, just moving closer or farther to it1
846
vect_temp = [robots[i].pos[0]-robots[it1].pos[0] ,
847
robots[i].pos[1]-robots[it1].pos[1]] # from it1 to i
848
ori_temp = math.atan2(vect_temp[1], vect_temp[0])
849
if dist_table[i][it1] > line_space:
850
# too far away, move closer to it1
851
robots[i].ori = reset_radian(ori_temp + math.pi)
852
robots[i].vel = (dist_table[i][it1] - line_space) * adjust_vel_coef
853
else:
854
# too close, move farther away from it1
855
robots[i].ori = ori_temp
856
robots[i].vel = (line_space - dist_table[i][it1]) * adjust_vel_coef
857
elif robots[i].status_2_end:
858
# robot 'i' is at the large index end
859
it0 = robots[i].key_neighbors[0]
860
# update the first merge availability
861
if dist_table[i][it0] > merge_min_dist:
862
robots[i].status_2_avail1[0] = True
863
else:
864
robots[i].status_2_avail1[0] = True
865
# update the moving direction and velocity
866
vect_temp = [robots[i].pos[0]-robots[it0].pos[0],
867
robots[i].pos[1]-robots[it0].pos[1]] # from it0 to i
868
ori_temp = math.atan2(vect_temp[1], vect_temp[0])
869
if dist_table[i][it0] > line_space:
870
# too far away, move closer to it0
871
robots[i].ori = reset_radian(ori_temp + math.pi/2)
872
robots[i].vel = (dist_table[i][it0] - line_space) * adjust_vel_coef
873
else:
874
# too close, move farther away from it0
875
robots[i].ori = ori_temp
876
robots[i].vel = (line_space - dist_table[i][it0]) * adjust_vel_coef
877
else:
878
# robot 'i' is at no end
879
it0 = robots[i].key_neighbors[0]
880
it1 = robots[i].key_neighbors[1]
881
# update the first merge availability
882
if dist_table[i][it0] > merge_min_dist:
883
robots[i].status_2_avail1[0] = True
884
else:
885
robots[i].status_2_avail1[0] = False
886
if dist_table[i][it1] > merge_min_dist:
887
robots[i].status_2_avail1[1] = True
888
else:
889
robots[i].status_2_avail1[1] = False
890
# update the moving direction and velocity
891
des_new = [(robots[it0].pos[0]+robots[it1].pos[0])/2,
892
(robots[it0].pos[1]+robots[it1].pos[1])/2]
893
vect_temp = (des_new[0]-robots[i].pos[0],
894
des_new[1]-robots[i].pos[1]) # from robot 'i' to des
895
dist_temp = math.sqrt(vect_temp[0]*vect_temp[0] +
896
vect_temp[1]*vect_temp[1])
897
robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])
898
robots[i].vel = dist_temp * adjust_vel_coef
899
# decrease life time of robot '-1'
900
elif robots[i].status == -1:
901
robots[i].status_n1_life = robots[i].status_n1_life - frame_period/1000.0
902
# life time decrease of the groups
903
for g_it in groups.keys():
904
if groups[g_it][4]: continue # not decrease life of the dominant
905
groups[g_it][1] = groups[g_it][1] - frame_period/1000.0
906
907
# graphics update
908
screen.fill(background_color)
909
# draw the robots
910
for i in range(robot_quantity):
911
display_pos = world_to_display(robots[i].pos, world_size, screen_size)
912
# get color of the robot
913
color_temp = ()
914
if robots[i].status == 0:
915
color_temp = robot_0_color
916
elif robots[i].status == 1:
917
color_temp = robot_1_color
918
elif robots[i].status == 2:
919
color_temp = robot_2_color
920
elif robots[i].status == -1:
921
color_temp = robot_n1_color
922
# draw the robot as a small solid circle
923
pygame.draw.circle(screen, color_temp, display_pos, robot_size, 0) # fill the circle
924
pygame.display.update()
925
926
pygame.quit()
927
928
929
930