Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
yangliu28
GitHub Repository: yangliu28/swarm_formation_sim
Path: blob/master/line_formation_1.py
104 views
1
# first line formation simulation of swarm robots, using climbing method to form the line
2
3
# comments for research purpose:
4
# My previous line formation simulations in ROS utilize the relative positions of nearby
5
# neighbors, and linear fit a line and move to the desired position. It only works when
6
# there are many robots within range or robot has a large sensing range, and preferrably
7
# the swarm is aggregated. No communication is required though.
8
# In this simulation, I extended the initial situation such that robots are in random
9
# positions in a bounded area. They can move around and communicate with robot in range.
10
# If not in a bounded area, they might just run too far away without knowing it. All
11
# communications are single hops, each robot only gives the other robot information about
12
# itself, not information it gets from a third robot. That means all information is first
13
# hand. The idea is that, start initial wondering, each robot is trying to form the first
14
# line segment, or find a line it can contribute to, depends on which comes first. There
15
# will be more than one line being formed. To control which line should survice, each line
16
# is given a time of life, it decreases by time and increases when a new member join the
17
# line. The line disassembles when it runs out of life time, and never expire if the number
18
# of members exceed half of the total number of robots in the simulation, and becomes the
19
# dominant group. Another way a line disassembles itself is, when a line detects the exist
20
# of another line, the line with less members will be disassembled.
21
22
# comments for programming purpose:
23
# Four statuses have been designed to serve different stages of the robot in the simulation:
24
# '0': free, wondering around, not in group, available to form a group or join one
25
# '1': forming an initial line segment, or found a line and trying to climbing to ends
26
# '1_0': initial an line segment, '0' found another '0' and formed a group
27
# '1_1': join a line, climbing to the closer end
28
# '2': already in a line, remain in position
29
# '-1': free, wondering around, no interaction with nearby robots
30
# Only allowed status transitions are:
31
# forward: '-1'->'0', '0'->'1', '1'->'2'
32
# backward: '1'->'-1', '2'->'-1'
33
# Purpose of '-1' is, when a line is disassembled, if '1' and '2' becomes '0', they will
34
# immediately form the old line because they are already in position. So '-1' which has no
35
# interaction with nearly robots, acts as a transition to status '0'.
36
37
# line formation runs out of boundary:
38
# It's possible that a line is trying to continue out of the boundary. This is no good way
39
# to solve it, if keeping the proposed control algorithm. But reducing the occurrence can
40
# be done, by enabling a relatively large wall detection range. Robot will be running away
41
# before it really hits the wall, so it's like smaller virtual boundaries inside the window.
42
# But this virtual boundaries only work on robot '0', such that robot '1' or '2' wants to
43
# form a line out of virtual boundaries, it can.
44
45
46
import pygame
47
import math, random
48
from line_formation_1_robot import LFRobot
49
from formation_functions import *
50
51
pygame.init() # initialize pygame
52
53
# for display, window origin is at left up corner
54
screen_size = (1200, 1000) # width and height
55
background_color = (0, 0, 0) # black background
56
robot_0_color = (0, 255, 0) # for robot status '0', green
57
robot_1_color = (255, 153, 153) # for robot status '1', pink
58
robot_2_color = (255, 51, 0) # for robot status '2', red
59
robot_n1_color = (0, 51, 204) # for robot status '-1', blue
60
robot_size = 5 # robot modeled as dot, number of pixels in radius
61
62
# set up the simulation window and surface object
63
icon = pygame.image.load('icon_geometry_art.jpg')
64
pygame.display.set_icon(icon)
65
screen = pygame.display.set_mode(screen_size)
66
pygame.display.set_caption('Line Formation 1 Simulation - Climbing')
67
68
# for physics, origin is at left bottom corner, starting (0, 0)
69
# it's more natural to do the calculation in right hand coordiante system
70
# the continuous, physical world for the robots
71
world_size = (100.0, 100.0 * screen_size[1]/screen_size[0])
72
73
# variables to configure the simulation
74
robot_quantity = 30
75
# coefficient to resize the robot distribution, but always keep close to center
76
distrib_coef = 0.5
77
const_vel = 3.0 # all moving robots are moving at a constant speed
78
frame_period = 100 # updating period of the simulation and graphics, in ms
79
comm_range = 5.0 # communication range, the radius
80
line_space = comm_range * 0.7 # a little more than half the communication range
81
space_err = line_space * 0.1 # the error to determine the space is good
82
climb_space = line_space * 0.5 # climbing is half the line space along the line
83
life_incre = 8 # each new member add these seconds to the life of the group
84
group_id_upper_limit = 1000 # random integer as group id from 0 to this limit
85
n1_life_lower = 3 # lower limit of life time of being status '-1'
86
n1_life_upper = 8 # upper limit of life time of being status '-1'
87
vbound_gap_ratio = 0.15 # for the virtual boundaries inside the window
88
# ratio is the gap distance to the world size on that axis
89
90
# instantiate the robot swarm as list
91
robots = [] # container for all robots, index is also the identification
92
for i in range(robot_quantity):
93
# random position, away from the window edges
94
pos_temp = (((random.random()-0.5)*distrib_coef+0.5) * world_size[0],
95
((random.random()-0.5)*distrib_coef+0.5) * world_size[1])
96
vel_temp = const_vel
97
ori_temp = random.random() * 2*math.pi - math.pi # random in (-pi, pi)
98
object_temp = LFRobot(pos_temp, vel_temp, ori_temp)
99
robots.append(object_temp)
100
# instantiate the group variable as dictionary
101
groups = {}
102
# key is the group id, so two groups won't share same id
103
# value is a list
104
# 0.first element: the group size, including both status '2' and '1'
105
# 1.second element: life time remaining
106
# 2.third element: a list of robots on the line in adjacent order, status '2'
107
# 3.forth element: a list of robots off the line, not in order, status '1'
108
# 4.fifth element: true or false, being the dominant group
109
110
# instantiate a distance table for every pair of robots
111
# will calculate once for symmetric data
112
dist_table = [[-1.0 for j in range(robot_quantity)] for i in range(robot_quantity)]
113
114
# calculate the corner positions of virtual boundaries, for display
115
# left bottom corner
116
pos_lb = world_to_display([world_size[0]*vbound_gap_ratio,
117
world_size[1]*vbound_gap_ratio], world_size, screen_size)
118
# right bottom corner
119
pos_rb = world_to_display([world_size[0]*(1-vbound_gap_ratio),
120
world_size[1]*vbound_gap_ratio], world_size, screen_size)
121
# right top corner
122
pos_rt = world_to_display([world_size[0]*(1-vbound_gap_ratio),
123
world_size[1]*(1-vbound_gap_ratio)], world_size, screen_size)
124
# left top corner
125
pos_lt = world_to_display([world_size[0]*vbound_gap_ratio,
126
world_size[1]*(1-vbound_gap_ratio)], world_size, screen_size)
127
128
# the loop
129
sim_exit = False # simulation exit flag
130
sim_pause = False # simulation pause flag, pause at begining
131
timer_last = pygame.time.get_ticks() # return number of milliseconds after pygame.init()
132
timer_now = timer_last # initialize it with timer_last
133
while not sim_exit:
134
# exit the program
135
for event in pygame.event.get():
136
if event.type == pygame.QUIT:
137
sim_exit = True # exit with the close window button
138
if event.type == pygame.KEYUP:
139
if event.key == pygame.K_SPACE:
140
sim_pause = not sim_pause # reverse the pause flag
141
if (event.key == pygame.K_ESCAPE) or (event.key == pygame.K_q):
142
sim_exit = True # exit with ESC key or Q key
143
144
# skip the rest of the loop if paused
145
if sim_pause: continue
146
147
# update the physics, control rules and graphics at the same time
148
timer_now = pygame.time.get_ticks()
149
if (timer_now - timer_last) > frame_period:
150
timer_last = timer_now # reset timer
151
# prepare the distance data for every pair of robots
152
for i in range(robot_quantity):
153
for j in range(i+1, robot_quantity):
154
# status of '-1' does not involve in any connection, so skip
155
if (robots[i].status == -1) or (robots[j].status == -1):
156
dist_table[i][j] = -1.0
157
dist_table[j][i] = -1.0
158
continue # skip the rest
159
# it only covers the upper triangle without the diagonal
160
vect_temp = (robots[i].pos[0]-robots[j].pos[0],
161
robots[i].pos[1]-robots[j].pos[1])
162
dist_temp = math.sqrt(vect_temp[0]*vect_temp[0] +
163
vect_temp[1]*vect_temp[1])
164
if dist_temp <= comm_range:
165
# only record distance smaller than communication range
166
dist_table[i][j] = dist_temp
167
dist_table[j][i] = dist_temp
168
else: # ignore the neighbors too far away
169
dist_table[i][j] = -1.0
170
dist_table[j][i] = -1.0
171
# sort the distance in another table, record the index here
172
index_list = [[] for i in range(robot_quantity)] # index of neighbors in range
173
# find all robots with non-zero distance
174
for i in range(robot_quantity):
175
for j in range(robot_quantity):
176
if j == i: continue # skip the self, not necessary
177
if dist_table[i][j] > 0: index_list[i].append(j)
178
# sort the index_list in the order of increasing distance
179
for i in range(robot_quantity):
180
len_temp = len(index_list[i]) # number of neighbors
181
if (len_temp < 2): continue # no need to sort
182
else:
183
# bubble sort
184
for j in range(len_temp-1):
185
for k in range(len_temp-1-j):
186
if (dist_table[i][index_list[i][k]] >
187
dist_table[i][index_list[i][k+1]]):
188
# swap the position of the two index
189
index_temp = index_list[i][k]
190
index_list[i][k] = index_list[i][k+1]
191
index_list[i][k+1] = index_temp
192
# get the status list corresponds to the sorted index_list
193
status_list = [[] for i in range(robot_quantity)]
194
for i in range(robot_quantity):
195
for j in index_list[i]:
196
status_list[i].append(robots[j].status)
197
198
# instantiate the status transition variables, prepare for status check
199
# the priority to process them is in the following order
200
s_grab_on = {} # robot '0' grabs on robot '2', becoming '1'
201
# key is id of robot '0', value is id of robot '2'
202
s_init_form = {} # robot '0' initial forms with another '0', becoming '1'
203
# key is id of robot '0' that discovers other robot '0' in range
204
# value is a list of robot '0's that are in range, in order of increasing distance
205
s_form_done = {} # robot '1' finishes initial forming, becoming '2'
206
# key is group id
207
# value is a list of id of robot '1's that have finished initial forming
208
s_climb_done = {} # robot '1' finishes climbing, becoming '2'
209
# key is group id
210
# value is a list of id of robot '1's that have finished climbing
211
s_form_lost = [] # robot '1' gets lost during initial forming
212
# list of group id for the initial forming robots
213
s_climb_lost = [] # robot '1' gets lost during climbing
214
# list of robot id for the climbing robots
215
s_group_exp = [] # life time of a group naturally expires
216
# life of group id
217
s_disassemble = [] # disassemble triggerred by robot '1' or '2'
218
# list of lists of group id to be compared for disassembling
219
s_back_0 = [] # robot '-1' gets back to '0'
220
# list of robot id
221
222
# check 'robots' for any status change, and schedule and process in next step
223
for i in range(robot_quantity):
224
# for the host robot having status of '0'
225
if robots[i].status == 0:
226
# check if this robot has valid neighbors at all
227
if len(index_list[i]) == 0: continue; # skip the neighbor check
228
# process neighbors with stauts '2', highest priority
229
if 2 in status_list[i]:
230
# check the group attribution of all the '2'
231
# get the robot id and group id of the first '2'
232
current_index = status_list[i].index(2)
233
current_robot = index_list[i][current_index]
234
current_group = robots[current_robot].group_id
235
groups_temp = {current_group:[current_robot]}
236
# check if there is still '2' in the list
237
while 2 in status_list[i][current_index+1:]:
238
# indexing the next '2' from current_index+1
239
# update the current_index, current_robot, current_group
240
current_index = status_list[i].index(2, current_index+1)
241
current_robot = index_list[i][current_index]
242
current_group = robots[current_robot].group_id
243
# update groups_temp
244
if current_group in groups_temp.keys():
245
# append this robot in the existing group
246
groups_temp[current_group].append(current_robot)
247
else:
248
# add new group id in groups_temp and add this robot
249
groups_temp[current_group] = [current_robot]
250
# check if there are multiple groups detected from the '2'
251
target_robot = -1 # the target robot to grab on
252
if len(groups_temp.keys()) == 1:
253
# there is only one group detected
254
dist_min = 2*comm_range # start with a large dist
255
robot_min = -1 # corresponding robot with min distance
256
# search the closest '2'
257
for j in groups_temp.values()[0]:
258
if dist_table[i][j] < dist_min:
259
dist_min = dist_table[i][j]
260
robot_min = j
261
target_robot = robot_min
262
else:
263
# there is more than one group detected
264
# no need to disassemble any group yet
265
# compare which group has the most members in it
266
member_max = 0 # start with 0 number of members
267
group_max = -1 # corresponding group id with most members
268
for j in groups_temp.keys():
269
if len(groups_temp[j]) > member_max:
270
member_max = len(groups_temp[j])
271
group_max = j
272
# search the closeat '2' inside that group
273
dist_min = 2*comm_range
274
robot_min = -1
275
for j in groups_temp[group_max]:
276
if dist_table[i][j] < dist_min:
277
dist_min = dist_table[i][j]
278
robot_min = j
279
target_robot = robot_min
280
# target_robot located, prepare to grab on it
281
# status transition scheduled, '0' to '1'
282
s_grab_on[i] = target_robot
283
# process neighbors with status '1', second priority
284
elif 1 in status_list[i]:
285
# find the closest '1' and get bounced away by it
286
# it doesn't matter if the '1's belong to different group
287
dist_min = 2*comm_range
288
robot_min = -1
289
for j in range(len(status_list[i])):
290
if status_list[i][j] != 1: continue
291
if dist_table[i][index_list[i][j]] < dist_min:
292
dist_min = dist_table[i][index_list[i][j]]
293
robot_min = index_list[i][j]
294
# target robot located, the robot_min
295
# get bounced away from this robot, update the moving direction
296
vect_temp = (robots[i].pos[0] - robots[robot_min].pos[0],
297
robots[i].pos[1] - robots[robot_min].pos[1])
298
# orientation is pointing from robot_min to host
299
robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])
300
# process neighbors with status '0', least priority
301
else:
302
# establish a list of all '0', in order of increasing distance
303
# to be checked later if grouping is possible
304
# this list should be only '0's, already sorted
305
target_list = index_list[i][:]
306
# status transition scheduled, '0' forming intial group with '0'
307
s_init_form[i] = target_list
308
# for the host robot having status of '1'
309
elif robots[i].status == 1:
310
# status of '1' needs to checked and maintained constantly
311
# 1.check if the important group neighbors are still in range
312
neighbors_secured = True
313
for j in robots[i].key_neighbors:
314
if j not in index_list[i]:
315
neighbors_secured = False
316
break
317
if neighbors_secured == False:
318
# status transition scheduled, robot '1' gets lost, becoming '-1'
319
if robots[i].status_1_sub == 0:
320
# append the group id, disassemble the entire group
321
s_form_lost.append(robots[i].group_id)
322
else:
323
s_climb_lost.append(i) # append the robot id
324
else:
325
# all the key neighbors are in good position
326
# 2.dissemble check, get group attribution of all '1' and '2'
327
status_list_temp = status_list[i][:]
328
index_list_temp = index_list[i][:]
329
# pop out the '0' first
330
while 0 in status_list_temp:
331
index_temp = status_list_temp.index(0)
332
status_list_temp.pop(index_temp)
333
index_list_temp.pop(index_temp)
334
if len(index_list_temp) > 0: # ensure there are in-group robots around
335
# start the group attribution dictionary with first robot
336
groups_temp = {robots[index_list_temp[0]].group_id: [index_list_temp[0]]}
337
for j in index_list_temp[1:]: # then iterating from the second one
338
current_group = robots[j].group_id
339
if current_group in groups_temp.keys():
340
# append this robot in same group
341
groups_temp[current_group].append(j)
342
else:
343
# add new key in the groups_temp dictionary
344
groups_temp[current_group] = [j]
345
# check if there are multiple groups detected
346
if len(groups_temp.keys()) > 1:
347
# status transition scheduled, to disassemble groups
348
s_disassemble.append(groups_temp.keys())
349
# may produce duplicates in s_disassemble, not big problem
350
# 3.check if any neighbor transition needs to be done
351
if robots[i].status_1_sub == 0:
352
# host robot is in the initial forming phase
353
# check if the neighbor robot is in appropriate distance
354
if abs(dist_table[i][robots[i].key_neighbors[0]] -
355
line_space) < space_err:
356
# status transition scheduled, finish intial forming, '1' to '2'
357
g_it = robots[i].group_id
358
if g_it in s_form_done.keys():
359
s_form_done[g_it].append(i)
360
else:
361
s_form_done[g_it] = [i]
362
elif robots[i].status_1_sub == 1:
363
# host robot is in the climbing phase
364
# check if the grab-on robot is at the begining or end of the line
365
# if yes, no need to search new key neighbor, only check destination
366
if (robots[robots[i].key_neighbors[0]].status_2_sequence == 0 or
367
robots[robots[i].key_neighbors[0]].status_2_end == True):
368
# check if reaching the destination coordinates
369
vect_temp = (robots[i].pos[0]-robots[i].status_1_1_des[0],
370
robots[i].pos[1]-robots[i].status_1_1_des[1])
371
dist_temp = math.sqrt(vect_temp[0]*vect_temp[0] +
372
vect_temp[1]*vect_temp[1])
373
if dist_temp < space_err:
374
# status transition scheduled, finish climbing, '1' to '2'
375
g_it = robots[i].group_id
376
if g_it in s_climb_done.keys():
377
s_climb_done[g_it].append(i)
378
else:
379
s_climb_done[g_it] = [i]
380
else:
381
# grab-on robot is not at any end of the line yet, still climbing
382
# check if new key neighbor appears, if yes, update new key neighbor
383
# and update the new destination address
384
it0 = robots[i].key_neighbors[0] # initialize with the old key neighbor
385
# 'it' stands for index temp
386
if robots[i].status_1_1_dir == 0:
387
# assign the new key neighbor
388
it0 = groups[robots[i].group_id][2][robots[it0].status_2_sequence - 1]
389
else:
390
it0 = groups[robots[i].group_id][2][robots[it0].status_2_sequence + 1]
391
if it0 in index_list[i]:
392
# update new grab-on robot as the only key neighbor
393
robots[i].key_neighbors = [it0]
394
# calculate new destination for the climbing
395
if robots[it0].status_2_sequence == 0:
396
# if new grab-on robot is at the begining of the line
397
it1 = groups[robots[i].group_id][2][1] # second one in the line
398
# to calculate the new destination, should not just mirror the des
399
# from 'it1' with 'it0', it is not precise enough, error will accumulate
400
# robots[i].status_1_1_des = [2*robots[it0].pos[0] - robots[it1].pos[0],
401
# 2*robots[it0].pos[1] - robots[it1].pos[1]]
402
# ori_temp is pointing from 'it1' to 'it0'
403
ori_temp = math.atan2(robots[it0].pos[1]-robots[it1].pos[1],
404
robots[it0].pos[0]-robots[it1].pos[0])
405
robots[i].status_1_1_des = [robots[it0].pos[0] + line_space*math.cos(ori_temp),
406
robots[it0].pos[1] + line_space*math.sin(ori_temp)]
407
elif robots[it0].status_2_end == True:
408
# if new grab-on robot is at the end of the line
409
it1 = groups[robots[i].group_id][2][-2] # second inversely in the line
410
# robots[i].status_1_1_des = [2*robots[it0].pos[0] - robots[it1].pos[0],
411
# 2*robots[it0].pos[1] - robots[it1].pos[1]]
412
ori_temp = math.atan2(robots[it0].pos[1]-robots[it1].pos[1],
413
robots[it0].pos[0]-robots[it1].pos[0])
414
robots[i].status_1_1_des = [robots[it0].pos[0] + line_space*math.cos(ori_temp),
415
robots[it0].pos[1] + line_space*math.sin(ori_temp)]
416
else: # new grab-on robot is not at any end
417
it1 = 0 # index of the next promising key neighbor
418
if robots[i].status_1_1_dir == 0:
419
it1 = groups[robots[i].group_id][2][robots[it0].status_2_sequence - 1]
420
else:
421
it1 = groups[robots[i].group_id][2][robots[it0].status_2_sequence + 1]
422
# direction from current key neighbor(it0) to next promising key neighbor(it1)
423
dir_temp = math.atan2((robots[it1].pos[1]-robots[it0].pos[1]),
424
(robots[it1].pos[0]-robots[it0].pos[0]))
425
# direction from next promising key neighbor to new destination
426
if robots[i].status_1_1_side == 0:
427
# climbing at the left of the line, rotate ccw of pi/2
428
dir_temp = dir_temp + math.pi/2
429
else:
430
dir_temp = dir_temp - math.pi/2 # rotate cw of pi/2
431
# calculate new destination address
432
robots[i].status_1_1_des = [robots[it1].pos[0]+climb_space*math.cos(dir_temp),
433
robots[it1].pos[1]+climb_space*math.sin(dir_temp)]
434
# for the host robot having status of '2'
435
elif robots[i].status == 2:
436
# it's ok to check if key neighbors are still in range
437
# but key neighbors of '2' are also '2', all of them are static
438
# so skip this step
439
# dissemble check, for all the '1' and '2'
440
status_list_temp = status_list[i][:]
441
index_list_temp = index_list[i][:]
442
# pop out the '0' first
443
while 0 in status_list_temp:
444
index_temp = status_list_temp.index(0)
445
status_list_temp.pop(index_temp)
446
index_list_temp.pop(index_temp)
447
# start the group attribution dictionary with first robot
448
if len(index_list_temp) > 0: # ensure there are in-group robots around
449
groups_temp = {robots[index_list_temp[0]].group_id: [index_list_temp[0]]}
450
for j in index_list_temp[1:]: # then iterating from the second one
451
current_group = robots[j].group_id
452
if current_group in groups_temp:
453
groups_temp[current_group].append(j)
454
else:
455
groups_temp[current_group] = [j]
456
# check if there are multiple groups detected
457
if len(groups_temp.keys()) > 1:
458
# status transition scheduled, to disassemble groups
459
s_disassemble.append(groups_temp.keys())
460
# for the host robot having status of '-1'
461
elif robots[i].status == -1:
462
# check if life time expires, and get status back to '0'
463
if robots[i].status_n1_life < 0:
464
s_back_0.append(i)
465
466
# check 'groups' for any status change
467
for g_it in groups.keys():
468
if groups[g_it][4]: continue # already being dominant
469
if groups[g_it][0] > robot_quantity/2:
470
# the group has more than half the total number of robots
471
groups[g_it][4] = True # becoming dominant
472
groups[g_it][1] = 100.0 # a random large number
473
if groups[g_it][1] < 0: # life time of a group expires
474
# schedule operation to disassemble this group
475
s_group_exp.append(g_it)
476
477
# process the scheduled status change, in the order of the priority
478
# 1.s_grab_on, robot '0' grabs on robot '2', becoming '1'
479
for i in s_grab_on.keys():
480
# 1.update the 'robots' variable
481
robots[i].status = 1 # status becoming '1'
482
it0 = s_grab_on[i] # for the robot being grabed on, new key neighbor
483
# ie., robot 'i' grabs on robot 'it0'
484
robots[i].key_neighbors = [it0] # update the key neighbor
485
g_it = robots[it0].group_id # for the new group id, group index temp
486
robots[i].group_id = g_it # update group id
487
robots[i].status_1_sub = 1 # sub status '1' for climbing
488
# 2.update the 'groups' variable
489
groups[g_it][0] = groups[g_it][0] + 1 # increase group size by 1
490
groups[g_it][1] = groups[g_it][1] + life_incre # increase life time
491
groups[g_it][3].append(i)
492
# 3.deciding the climbing direction
493
if robots[it0].status_2_sequence > (len(groups[g_it][2])-1)/2:
494
robots[i].status_1_1_dir = 1
495
else:
496
robots[i].status_1_1_dir = 0
497
# 4.deciding which side the robot is climbing at
498
if robots[i].status_1_1_dir == 0: # search backward for the next robot
499
it1 = groups[g_it][2][robots[it0].status_2_sequence + 1]
500
else:
501
it1 = groups[g_it][2][robots[it0].status_2_sequence - 1]
502
# by calculating the determinant of vectors 'it1->i' and 'it1->it0'
503
if ((robots[it0].pos[0]-robots[it1].pos[0])*(robots[i].pos[1]-robots[it1].pos[1]) -
504
(robots[it0].pos[1]-robots[it1].pos[1])*(robots[i].pos[0]-robots[it1].pos[0])) >= 0:
505
# it's rare that the above determinant should equal to 0
506
robots[i].status_1_1_side = 0 # at left side
507
else:
508
robots[i].status_1_1_side = 1
509
# 5.calculate the initial destination
510
if robots[it0].status_2_sequence == 0:
511
it1 = groups[g_it][2][1] # adjacent robot id
512
robots[i].status_1_1_des = [2*robots[it0].pos[0] - robots[it1].pos[0],
513
2*robots[it0].pos[1] - robots[it1].pos[1]]
514
elif robots[it0].status_2_end == True:
515
it1 = groups[g_it][2][-2] # adjacent robot id
516
robots[i].status_1_1_des = [2*robots[it0].pos[0] - robots[it1].pos[0],
517
2*robots[it0].pos[1] - robots[it1].pos[1]]
518
else:
519
# robot it0 is not at the begining or end of the line
520
it1 = 0
521
if robots[i].status_1_1_dir == 0:
522
it1 = groups[robots[i].group_id][2][robots[it0].status_2_sequence - 1]
523
else:
524
it1 = groups[robots[i].group_id][2][robots[it0].status_2_sequence + 1]
525
# direction from current key neighbor(it0) to next promising key neighbor(it1)
526
dir_temp = math.atan2((robots[it1].pos[1]-robots[it0].pos[1]),
527
(robots[it1].pos[0]-robots[it0].pos[0]))
528
# direction from next promising key neighbor to new destination
529
if robots[i].status_1_1_side == 0:
530
# climbing at the left of the line, rotate ccw of pi/2
531
dir_temp = dir_temp + math.pi/2
532
else:
533
dir_temp = dir_temp - math.pi/2 # rotate cw of pi/2
534
# calculate new destination address
535
robots[i].status_1_1_des = [robots[it1].pos[0]+climb_space*math.cos(dir_temp),
536
robots[it1].pos[1]+climb_space*math.sin(dir_temp)]
537
# update the moving orientation
538
robots[i].ori = math.atan2(robots[i].status_1_1_des[1]-robots[i].pos[1],
539
robots[i].status_1_1_des[0]-robots[i].pos[0])
540
# 2.s_init_form, robot '0' initial forms with another '0', becoming '1'
541
s_pair = [] # the container for finalized initial forming pairs
542
while len(s_init_form.keys()) != 0: # there are still robots to be processed
543
for i in s_init_form.keys():
544
it = s_init_form[i][0] # index temp
545
if it in s_init_form.keys():
546
if s_init_form[it][0] == i: # robot 'it' also recognizes 'i' as closest
547
s_pair.append([i, it])
548
s_init_form.pop(i) # pop out both 'i' and 'it'
549
s_init_form.pop(it)
550
break
551
elif i not in s_init_form[it]:
552
# usually should not be here unless robots have different sensing range
553
s_init_form[i].remove(it)
554
if len(s_init_form[i]) == 0:
555
s_init_form.pop(i)
556
break
557
# have not consider the situation that 'i' in 'it' but not first one
558
else:
559
# will be here if robot 'it' chooses to be bounced away by '1', or grab on '2'
560
s_init_form[i].remove(it)
561
if len(s_init_form[i]) == 0:
562
s_init_form.pop(i)
563
break
564
# process the finalized pairs
565
for pair in s_pair:
566
it0 = pair[0] # get indices of the pair
567
it1 = pair[1]
568
g_it = random.randint(0, group_id_upper_limit)
569
while g_it in groups.keys():
570
g_it = random.randint(0, group_id_upper_limit) # generate again
571
# update the 'robots' variable
572
robots[it0].status = 1
573
robots[it1].status = 1
574
robots[it0].group_id = g_it
575
robots[it1].group_id = g_it
576
robots[it0].status_1_sub = 0 # sub status '0' for initial climbing
577
robots[it1].status_1_sub = 0
578
robots[it0].key_neighbors = [it1] # name the other as the key neighbor
579
robots[it1].key_neighbors = [it0]
580
# update the 'groups' variable
581
groups[g_it] = [2, 2*life_incre, [], [it0, it1], False] # add new entry
582
# deciding moving direction for the initial forming robots
583
vect_temp = (robots[it1].pos[0]-robots[it0].pos[0],
584
robots[it1].pos[1]-robots[it0].pos[1]) # pointing from it0 to it1
585
ori_temp = math.atan2(vect_temp[1], vect_temp[0])
586
if dist_table[it0][it1] > line_space: # equally dist_table[it1][it0]
587
# attracting each other, most likely this happens
588
robots[it0].ori = ori_temp
589
robots[it1].ori = reset_radian(ori_temp + math.pi)
590
else:
591
# repelling each other
592
robots[it0].ori = reset_radian(ori_temp + math.pi)
593
robots[it1].ori = ori_temp
594
# no need to check if they are already within the space error of line space
595
# this will be done in the next round of status change check
596
# 3.s_form_done, robot '1' finishes intial forming, becoming '2'
597
for g_it in s_form_done.keys():
598
if len(s_form_done[g_it]) == 2: # double check, both robots agree forming is done
599
it0 = s_form_done[g_it][0]
600
it1 = s_form_done[g_it][1]
601
# update 'robots' variable for 'it0' and 'it1'
602
robots[it0].status = 2
603
robots[it1].status = 2
604
# randomly deciding which is begining and end of the line, random choice
605
if random.random() > 0.5: # half chance
606
# swap 'ito' and 'it1', 'it0' as begining, 'it1' as end
607
temp = it0
608
it0 = it1
609
it1 = temp
610
# update the 'robots' variable
611
robots[it0].status_2_sequence = 0
612
robots[it1].status_2_sequence = 1
613
robots[it0].status_2_end = False
614
robots[it1].status_2_end = True
615
robots[it0].key_neighbors = [it1] # can skip this
616
robots[it1].key_neighbors = [it0]
617
# update the 'groups' variable
618
g_it = robots[it0].group_id
619
groups[g_it][2] = [it0, it1] # add the robots for the line
620
groups[g_it][3] = [] # empty the forming & climbing pool
621
# 4.s_climb_done, robot '1' finishes climbing, becoming '2'
622
for g_it in s_climb_done.keys():
623
start_pool = [] # for robots finish at the begining
624
end_pool = [] # for robots finish at the end
625
for i in s_climb_done[g_it]:
626
if robots[i].status_1_1_dir == 0:
627
start_pool.append(i)
628
else:
629
end_pool.append(i)
630
if len(start_pool) > 0: # start pool not empty
631
# most likely there is only one robot in the pool
632
# randomly choose one just in case, may not be the best way
633
it0 = random.choice(start_pool)
634
# no need to care the not selected, they will continue climbing
635
# upate the 'robots' variable
636
robots[it0].status = 2
637
robots[it0].status_2_sequence = 0 # make it begining of the line
638
robots[it0].status_2_end = False
639
robots[it0].key_neighbors = [groups[g_it][2][0]]
640
robots[groups[g_it][2][0]].key_neighbors.append(it0) # add a new key neighbor
641
# update the 'groups' variable
642
groups[g_it][3].remove(it0) # remove from the climbing pool
643
groups[g_it][2].insert(0, it0) # insert new index at the begining
644
# increase the status_2_sequence of other robots on the line by 1
645
for i in groups[g_it][2][1:]:
646
robots[i].status_2_sequence = robots[i].status_2_sequence + 1
647
if len(end_pool) > 0: # end pool not empty
648
it0 = random.choice(end_pool)
649
# update the 'robots' variable
650
robots[it0].status = 2
651
robots[it0].status_2_sequence = len(groups[g_it][2])
652
robots[it0].status_2_end = True
653
it1 = groups[g_it][2][-1] # the old end of the line
654
robots[it1].status_2_end = False
655
robots[it0].key_neighbors = [it1]
656
robots[it1].key_neighbors.append(it0)
657
# update the 'groups' variable
658
groups[g_it][3].remove(it0) # remove from the climbing pool
659
groups[g_it][2].append(it0) # append 'it0' at the end of the line
660
# 5.s_form_lost, robot '1' gets lost during initial forming
661
for g_it in s_form_lost:
662
# can disassemble the group here, but may as well do it together in s_disassemble
663
s_disassemble.append([g_it])
664
# 6.s_climb_lost, robot '1' gets lost during climbing, becoming '-1'
665
for i in s_climb_lost:
666
# update the 'robots' variable
667
robots[i].status = -1
668
# a new random moving orientation
669
robots[i].ori = random.random() * 2*math.pi - math.pi
670
# a new random life time
671
robots[i].status_n1_life = random.randint(n1_life_lower, n1_life_upper)
672
# update the 'groups' variable
673
g_it = robots[i].group_id
674
groups[g_it][0] = groups[g_it][0] - 1 # reduce group size by 1
675
# life time of a group doesn't decrease due to member lost
676
groups[g_it][3].remove(i) # remove the robot from the pool list
677
# 7.s_group_exp, natural life expiration of the groups
678
for g_it in s_group_exp:
679
s_disassemble.append([g_it]) # leave it to s_disassemble
680
# 8.s_disassemble, triggered by robot '1' or '2'
681
s_dis_list = [] # list of group id that has been finalized for disassembling
682
# compare number of members to decide which groups to disassemble
683
for gs_it in s_disassemble:
684
if len(gs_it) == 1:
685
# from the s_form_lost
686
if gs_it[0] not in s_dis_list:
687
s_dis_list.append(gs_it[0])
688
else:
689
# compare which group has the most members, and disassemble the rest
690
g_temp = gs_it[:]
691
member_max = 0 # number of members in the group
692
group_max = -1 # corresponding group id with most members
693
for g_it in g_temp:
694
if groups[g_it][0] > member_max:
695
member_max = groups[g_it][0]
696
group_max = g_it
697
g_temp.remove(group_max) # remove the group with the most members
698
for g_it in g_temp:
699
if g_it not in s_dis_list: # avoid multiple occurrence
700
s_dis_list.append(g_it)
701
# start disassembling
702
for g_it in s_dis_list:
703
# update the 'robots' variable
704
for i in groups[g_it][3]: # for robots off the line
705
robots[i].status = -1
706
# give a random (integer) life time in designed range
707
robots[i].status_n1_life = random.randint(n1_life_lower, n1_life_upper)
708
robots[i].ori = random.random() * 2*math.pi - math.pi
709
# give a random moving orientation is still not good enough
710
# a new way for the robots on the line is 'explode' the first half to one side
711
# and 'explode' the second half to the other side.
712
num_online = len(groups[g_it][2]) # number of robots on the line
713
num_1_half = num_online/2 # number of robots for the first half
714
num_2_half = num_online - num_1_half # number of robots for the second half
715
for i in groups[g_it][2]: # for robots on the line
716
robots[i].status = -1
717
robots[i].status_n1_life = random.randint(n1_life_lower, n1_life_upper)
718
# robots[i].ori = random.random() * 2*math.pi - math.pi
719
# first deciding the direction of the line, always pointing to the closer end
720
ori_temp = 0
721
seq_temp = robots[i].status_2_sequence # sequence on the line
722
if robots[i].status_2_sequence == 0:
723
# then get second one on the line
724
it0 = groups[robots[i].group_id][2][1]
725
# orientation points to the small index end
726
ori_temp = math.atan2(robots[i].pos[1]-robots[it0].pos[1],
727
robots[i].pos[0]-robots[it0].pos[0])
728
elif robots[i].status_2_end == True:
729
# then get inverse second one on the line
730
it0 = groups[robots[i].group_id][2][-2]
731
# orientation points to the large index end
732
ori_temp = math.atan2(robots[i].pos[1]-robots[it0].pos[1],
733
robots[i].pos[0]-robots[it0].pos[0])
734
else:
735
# get both neighbors
736
it0 = groups[g_it][2][seq_temp - 1]
737
it1 = groups[g_it][2][seq_temp + 1]
738
if seq_temp < num_1_half: # robot in the first half
739
# orientation points to the small index end
740
ori_temp = math.atan2(robots[it0].pos[1]-robots[it1].pos[1],
741
robots[it0].pos[0]-robots[it1].pos[0])
742
else:
743
# orientation points to the large index end
744
ori_temp = math.atan2(robots[it1].pos[1]-robots[it0].pos[1],
745
robots[it1].pos[0]-robots[it0].pos[0])
746
# then calculate the 'explosion' direction
747
if seq_temp < num_1_half:
748
robots[i].ori = reset_radian(ori_temp + math.pi*(seq_temp+1)/(num_1_half+1))
749
# always 'explode' to the left side
750
else:
751
robots[i].ori = reset_radian(ori_temp + math.pi*(num_online-seq_temp)/(num_2_half+1))
752
# pop out this group in 'groups'
753
groups.pop(g_it)
754
# 9.s_back_0, life time of robot '-1' expires, becoming '0'
755
for i in s_back_0:
756
# still maintain the old moving direction
757
robots[i].status = 0
758
759
# update the physics(pos and ori), and wall bouncing, life decrease of '-1'
760
for i in range(robot_quantity):
761
if robots[i].status == 2:
762
continue # every robot moves except '2'
763
# check if out of boundaries, algorithms revised from 'experiment_3_automaton'
764
# change only direction of velocity
765
if robots[i].status != 0:
766
# for the robots '-1', '1' and '2', bounded by the real boundaries
767
if robots[i].pos[0] >= world_size[0]: # out of right boundary
768
if math.cos(robots[i].ori) > 0: # velocity on x is pointing right
769
robots[i].ori = reset_radian(2*(math.pi/2) - robots[i].ori)
770
elif robots[i].pos[0] <= 0: # out of left boundary
771
if math.cos(robots[i].ori) < 0: # velocity on x is pointing left
772
robots[i].ori = reset_radian(2*(math.pi/2) - robots[i].ori)
773
if robots[i].pos[1] >= world_size[1]: # out of top boundary
774
if math.sin(robots[i].ori) > 0: # velocity on y is pointing up
775
robots[i].ori = reset_radian(2*(0) - robots[i].ori)
776
elif robots[i].pos[1] <= 0: # out of bottom boundary
777
if math.sin(robots[i].ori) < 0: # velocity on y is pointing down
778
robots[i].ori = reset_radian(2*(0) - robots[i].ori)
779
else:
780
# for robot '0' only, bounded in a smaller area
781
# so more lines will be formed in the center area, and line won't run out
782
if robots[i].pos[0] >= world_size[0]*(1-vbound_gap_ratio):
783
if math.cos(robots[i].ori) > 0:
784
robots[i].ori = reset_radian(2*(math.pi/2) - robots[i].ori)
785
elif robots[i].pos[0] <= world_size[0]*vbound_gap_ratio:
786
if math.cos(robots[i].ori) < 0:
787
robots[i].ori = reset_radian(2*(math.pi/2) - robots[i].ori)
788
if robots[i].pos[1] >= world_size[1]*(1-vbound_gap_ratio):
789
if math.sin(robots[i].ori) > 0:
790
robots[i].ori = reset_radian(2*(0) - robots[i].ori)
791
elif robots[i].pos[1] <= world_size[1]*vbound_gap_ratio:
792
if math.sin(robots[i].ori) < 0:
793
robots[i].ori = reset_radian(2*(0) - robots[i].ori)
794
# update one step of distance
795
travel_dist = robots[i].vel * frame_period/1000.0
796
robots[i].pos[0] = robots[i].pos[0] + travel_dist*math.cos(robots[i].ori)
797
robots[i].pos[1] = robots[i].pos[1] + travel_dist*math.sin(robots[i].ori)
798
# update direction of velocity for robot '1', conflict with boundary check?
799
if robots[i].status == 1:
800
if robots[i].status_1_sub == 0:
801
it0 = robots[i].key_neighbors[0]
802
vect_temp = (robots[it0].pos[0]-robots[i].pos[0],
803
robots[it0].pos[1]-robots[i].pos[1]) # pointing from i to it0
804
ori_temp = math.atan2(vect_temp[1], vect_temp[0])
805
if dist_table[i][it0] > line_space: # it's ok to use the out dated dist_table
806
# pointing toward the neighbor
807
robots[i].ori = ori_temp
808
else:
809
# pointing opposite the neighbor
810
robots[i].ori = reset_radian(ori_temp + math.pi)
811
else:
812
robots[i].ori = math.atan2(robots[i].status_1_1_des[1]-robots[i].pos[1],
813
robots[i].status_1_1_des[0]-robots[i].pos[0])
814
# decrease life time of robot with status '-1'
815
if robots[i].status == -1:
816
robots[i].status_n1_life = robots[i].status_n1_life - frame_period/1000.0
817
# life time decrease of the groups
818
for g_it in groups.keys():
819
if groups[g_it][4]: continue # not decrease life of the dominant
820
groups[g_it][1] = groups[g_it][1] - frame_period/1000.0
821
822
# graphics update
823
screen.fill(background_color)
824
# draw the virtual boundaries
825
pygame.draw.lines(screen, (255, 255, 255), True, [pos_lb, pos_rb, pos_rt, pos_lt], 1)
826
# draw the robots
827
for i in range(robot_quantity):
828
display_pos = world_to_display(robots[i].pos, world_size, screen_size)
829
# get color of the robot
830
color_temp = ()
831
if robots[i].status == 0:
832
color_temp = robot_0_color
833
elif robots[i].status == 1:
834
color_temp = robot_1_color
835
elif robots[i].status == 2:
836
color_temp = robot_2_color
837
elif robots[i].status == -1:
838
color_temp = robot_n1_color
839
# draw the robot as a small solid circle
840
pygame.draw.circle(screen, color_temp, display_pos, robot_size, 0) # fill the circle
841
pygame.display.update()
842
843
pygame.quit()
844
845
846
847