# second line formation simulation, using merging method instead of climbing1# this program shared the same framework with previous line formation simulation program23# merging method analysis(compared to climbing):4# The line may not be as straight as in first simulation, and it would take time to stretch5# the line so that robots are evenly spaced. But merging method has the flexibility to6# adjust the formation to walls and obstacles, so there is no need to use double walls to7# make sure the initial line segments start in the middle to avoid the line hit the wall.89# comments for research purpose:10# Same competing mechanism is used to ensure only one line is being formed.1112# comments for programming purpose:13# Similar four statuses have been designed to serve different stages of the robot:14# '0': free, wondering around, not in group, available to form a group or join one15# '1': forming an initial line segment, or found a line and trying to merge into it16# '1_0': initial an line segment, '0' found another '0' and formed a group17# '1_1': join a line, merging into the closest spot18# '2': already in a line, dynamically adjust space and straightness19# '-1': free, wondering around, no interaction with nearby robots20# Only allowed status transitions are:21# forward: '-1'->'0', '0'->'1', '1'->'2'22# backward: '1'->'-1', '2'->'-1'23# The 'key_neighbors' attribute carries more information than in previous line simulation.2425# make the line straight and evenly spaced:26# A simple proportional control method was used here. The robots on the line '2' never stops27# adjusting, there is no threshold for the distance error. The robot is always heading toward28# the calculated target position. And to stablize the tracking, the moving velocity will29# decrease proportionally to the distance error.3031import pygame32import math, random33from line_formation_2_robot import LFRobot34from formation_functions import *3536pygame.init()3738# add music as background, just for fun :)39pygame.mixer.music.load('audio/the-flight-of-the-bumblebee.ogg')40pygame.mixer.music.play(-1) # -1 for playing infinite times41pygame.mixer.music.set_volume(0.5) # half volume4243# for display, window origin is at left up corner44screen_size = (1200, 1000) # width and height45background_color = (0, 0, 0) # black background46robot_0_color = (0, 255, 0) # for robot status '0', green47robot_1_color = (255, 153, 153) # for robot status '1', pink48robot_2_color = (255, 51, 0) # for robot status '2', red49robot_n1_color = (0, 51, 204) # for robot status '-1', blue50robot_size = 5 # robot modeled as dot, number of pixels in radius5152# set up the simulation window and surface object53icon = pygame.image.load('icon_geometry_art.jpg')54pygame.display.set_icon(icon)55screen = pygame.display.set_mode(screen_size)56pygame.display.set_caption('Line Formation 2 Simulation - Merging')5758# for physics, continuous world, origin is at left bottom corner, starting (0, 0),59# with x axis pointing right, y axis pointing up.60# It's more natural to compute the physics in right hand coordiante system.61world_size = (100.0, 100.0 * screen_size[1]/screen_size[0])6263# varialbes to configure the simulation64robot_quantity = 3065# coefficient to resize the robot distribution, to keep initial positions to center66distrib_coef = 0.567const_vel = 3.0 # all robots except '2' are moving at this faster constant speed68frame_period = 100 # updating period of the simulation and graphics, in ms69comm_range = 5.0 # sensing and communication range, the radius70line_space = comm_range * 0.7 # line space, between half of and whole communication range71merge_min_dist = line_space * 0.7 # minimum distance between two robots to allow merge72space_err = line_space * 0.1 # error to determine the space is good when robot arrives73life_incre = 8 # number of seconds a new member adds to a group74group_id_upper_limit = 1000 # upper limit of random integer for group id75n1_life_lower = 3 # lower limit of life time for status '-1'76n1_life_upper = 8 # upper limit of life time for status '-1'77# coefficient for calculating velocity of robot '2' on the line for adjusting78# the smaller the distance error, the smaller the velocity79# tune this parameter to adjust the line more quickly and stably80adjust_vel_coef = const_vel/line_space * 28182# instantiate the robot swarm as list83robots = [] # container for all robots, index is its identification84for i in range(robot_quantity):85# random position, away from the window's edges86pos_temp = (((random.random()-0.5)*distrib_coef+0.5) * world_size[0],87((random.random()-0.5)*distrib_coef+0.5) * world_size[1])88vel_temp = const_vel89ori_temp = random.random() * 2*math.pi - math.pi # random in (-pi, pi)90object_temp = LFRobot(pos_temp, vel_temp, ori_temp)91robots.append(object_temp)92# instantiate the group variable as dictionary93groups = {}94# key is the group id95# value is a list96# 0.first element: the group size, member includes both '2' and '1'97# 1.second element: remaining life time98# 2.third element: a list of robots on the line in adjacent order, status '2'99# 3.fourth element: a list of robots off the line, not in order, status '1'100# 4.fifth element: true or false, being the domianant group101# instantiate a distance table for every pair of robots102# make sure all data in table is being written when updating103dist_table = [[0 for j in range(robot_quantity)] for i in range(robot_quantity)]104105# the loop106sim_exit = False # simulation exit flag107sim_pause = False # simulation pause flag108timer_last = pygame.time.get_ticks() # return number of milliseconds after pygame.init()109timer_now = timer_last # initialize it with timer_last110while not sim_exit:111# exit the program112for event in pygame.event.get():113if event.type == pygame.QUIT:114sim_exit = True # exit with the close window button115if event.type == pygame.KEYUP:116if event.key == pygame.K_SPACE:117sim_pause = not sim_pause # reverse the pause flag118if (event.key == pygame.K_ESCAPE) or (event.key == pygame.K_q):119sim_exit = True # exit with ESC key or Q key120121# skip the rest of the loop if paused122if sim_pause: continue123124# update the physics, control rules and graphics at the same time125timer_now = pygame.time.get_ticks()126if (timer_now - timer_last) > frame_period:127timer_last = timer_now # reset timer128# prepare the distance data for every pair of robots129for i in range(robot_quantity):130for j in range(i+1, robot_quantity):131# status of '-1' does not involve in any connection, so skip132if (robots[i].status == -1) or (robots[j].status == -1):133dist_table[i][j] = -1.0134dist_table[j][i] = -1.0135continue # skip the rest136# it only covers the upper triangle without the diagonal137vect_temp = (robots[i].pos[0]-robots[j].pos[0],138robots[i].pos[1]-robots[j].pos[1])139dist_temp = math.sqrt(vect_temp[0]*vect_temp[0] +140vect_temp[1]*vect_temp[1])141if dist_temp <= comm_range:142# only record distance smaller than communication range143dist_table[i][j] = dist_temp144dist_table[j][i] = dist_temp145else: # ignore the neighbors too far away146dist_table[i][j] = -1.0147dist_table[j][i] = -1.0148# sort the distance in another table, record the index here149index_list = [[] for i in range(robot_quantity)] # index of neighbors in range150# find all robots with non-zero distance151for i in range(robot_quantity):152for j in range(robot_quantity):153if j == i: continue # skip the self, not necessary154if dist_table[i][j] > 0: index_list[i].append(j)155# sort the index_list in the order of increasing distance156for i in range(robot_quantity):157len_temp = len(index_list[i]) # number of neighbors158if (len_temp < 2): continue # no need to sort159else:160# bubble sort161for j in range(len_temp-1):162for k in range(len_temp-1-j):163if (dist_table[i][index_list[i][k]] >164dist_table[i][index_list[i][k+1]]):165# swap the position of the two index166index_temp = index_list[i][k]167index_list[i][k] = index_list[i][k+1]168index_list[i][k+1] = index_temp169# get the status list corresponds to the sorted index_list170status_list = [[] for i in range(robot_quantity)]171for i in range(robot_quantity):172for j in index_list[i]:173status_list[i].append(robots[j].status)174175# instantiate the status transition variables, for the status check process176# the priority to process them is in the following order177s_grab_on = {} # robot '0' grabs on robot '2', becoming '1'178# key is id of robot '0', value is id of robot '2'179s_init_form = {} # robot '0' initial forms with another '0', becoming '1'180# key is id of robot '0' that discovers other robot '0' in range181# value is a list of robot '0's that are in range, in order of increasing distance182s_form_done = {} # robot '1' finishes initial forming, becoming '2'183# key is group id184# value is a list of id of robot '1's that have finished initial forming185s_merge_done = [] # robot '1' finishes merging, becoming '2'186# list of robot '1' that finished merging187# s_form_lost = [] # robot '1' gets lost during initial forming188# # list of group id for the initial forming robots189# s_merge_lost = [] # robot '1' gets lost during merging190# # list of robot id for the merging robots191s_line_lost = [] # robot '2' gets lost during adjusting pos192# list of group id for the robots get lost in that group193s_group_exp = [] # life time of a group naturally expires194# life of group id195s_disassemble = [] # disassemble triggerred by robot '1' or '2'196# list of lists of group id to be compared for disassembling197s_back_0 = [] # robot '-1' gets back to '0'198# list of robot id199200# check 'robots' for any status change, schedule them for processing in next step201for i in range(robot_quantity):202# for the host robot having status of '0'203if robots[i].status == 0:204# check if this robot has valid neighbors at all205if len(index_list[i]) == 0: continue # skip the neighbor check206# process neighbors with status '2', highest priority207if 2 in status_list[i]:208# check the group attribution of all the '2'209# get the robot id and group id of the first '2'210current_index = status_list[i].index(2)211current_robot = index_list[i][current_index]212current_group = robots[current_robot].group_id213groups_temp = {current_group: [current_robot]}214# check if there is still '2' in the list215while 2 in status_list[i][current_index+1:]:216# indexing the next '2' from current_index+1217# update the current_index, current_robot, current_group218current_index = status_list[i].index(2, current_index+1)219current_robot = index_list[i][current_index]220current_group = robots[current_robot].group_id221# update groups_temp222if current_group in groups_temp.keys():223# append this robot in the existing group224groups_temp[current_group].append(current_robot)225else:226# add new group id in groups_temp and add this robot227groups_temp[current_group] = [current_robot]228# check if there are multiple groups detected from the '2'229target_robot = 0 # the target robot to grab on230if len(groups_temp.keys()) == 1:231# there is only one group detected232dist_min = 2*comm_range # smallest distance, start with large one233robot_min = -1 # corresponding robot with min distance234# search the closest '2'235for j in groups_temp.values()[0]:236if dist_table[i][j] < dist_min:237if ((robots[j].status_2_avail1[0] & robots[j].status_2_avail2[0]) |238(robots[j].status_2_avail1[1] & robots[j].status_2_avail2[1])):239# check both avail1 and avail2 for both side240# there is at least one side is available to be merged241dist_min = dist_table[i][j]242robot_min = j243target_robot = robot_min244else:245# there is more than one group detected246# it is designed that no disassembling trigger from robot '0'247# compare which group has the most members in it248member_max = 0 # start with 0 number of members in group249group_max = 0 # corresponding group id with most members250for j in groups_temp.keys():251## 'line_formation_1.py' did wrong in the following line252# didn't fix it, not serious problem, program rarely goes here253if groups[j][0] > member_max:254member_max = groups[j][0]255group_max = j256# search the closest '2' inside that group257dist_min = 2*comm_range258robot_min = -1259for j in groups_temp[group_max]:260if dist_table[i][j] < dist_min:261if ((robots[j].status_2_avail1[0] & robots[j].status_2_avail2[0]) |262(robots[j].status_2_avail1[1] & robots[j].status_2_avail2[1])):263dist_min = dist_table[i][j]264robot_min = j265target_robot = robot_min266# check if target robot has been located, prepare to grab on it267if target_robot != -1: # not the initial value of robot_min268# the target robot should have at least one spot availbe to be merged269s_grab_on[i] = target_robot270# process neighbors with status '1', second priority271elif 1 in status_list[i]:272# find the closest '1' and get bounced away by it273# still no trigger for disassembling if multiple groups exist in the '1's274dist_min = 2*comm_range275robot_min = -1276for j in range(len(status_list[i])):277if status_list[i][j] != 1: continue278if dist_table[i][index_list[i][j]] < dist_min:279dist_min = dist_table[i][index_list[i][j]]280robot_min = index_list[i][j]281# target robot located, the robot_min, should not still be -1 here282# get bounced away from this robot, update the moving direction283vect_temp = (robots[i].pos[0] - robots[robot_min].pos[0],284robots[i].pos[1] - robots[robot_min].pos[1])285# orientation is pointing from robot_min to host286robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])287# process neighbors with status '0', least priority288else:289# establish a list of all '0', in order of increasing distance290# to be checked later if grouping is possible and no conflict291# this list should be only '0's, already sorted292target_list = index_list[i][:]293# status transition scheduled, '0' forming initial group with '0'294s_init_form[i] = target_list[:]295# for the host robot having status of '1'296elif robots[i].status == 1:297# status of '1' needs to be checked and maintained constantly298# 1.check if the important group neighbors are still in range (skiped)299# one of key neighbors of robot '1' may appears later when merging,300# and key_neighbor variable may has -1 value representing empty301# 2.disassemble check, get group attribution of all '1' and '2'302status_list_temp = status_list[i][:]303index_list_temp = index_list[i][:]304# pop out the '0' first305while 0 in status_list_temp:306index_temp = status_list_temp.index(0)307status_list_temp.pop(index_temp)308index_list_temp.pop(index_temp)309if len(index_list_temp) > 0: # ensure at least one in-group robot around310# start the group attribution dictionary with first robot311groups_temp = {robots[index_list_temp[0]].group_id: [index_list_temp[0]]}312for j in index_list_temp[1:]: # iterating from the second one313current_group = robots[j].group_id314if current_group in groups_temp.keys():315# append this robot in same group316groups_temp[current_group].append(j)317else:318# add new key in the groups_temp dictionary319groups_temp[current_group] = [j]320# check if there are multiple groups detected321if len(groups_temp.keys()) > 1:322# status transition scheduled, to disassemble groups323s_disassemble.append(groups_temp.keys())324# may produce duplicates in s_disassemble, not serious problem325# 3.check if any status transition needs to be done326if robots[i].status_1_sub == 0:327# host robot is in the initial forming phase328# check if the neighbor robot is in appropriate distance329if abs(dist_table[i][robots[i].key_neighbors[0]] -330line_space) < space_err:331# status transition scheduled, finish initial forming, '1' to '2'332g_it = robots[i].group_id333if g_it in s_form_done.keys():334s_form_done[g_it].append(i)335else:336s_form_done[g_it] = [i]337elif robots[i].status_1_sub == 1:338# host robot is in the merging phase339# check if the merging robot reaches the destination340vect_temp = (robots[i].status_1_1_des[0] - robots[i].pos[0],341robots[i].status_1_1_des[1] - robots[i].pos[1])342dist_temp = math.sqrt(vect_temp[0]*vect_temp[0] +343vect_temp[1]*vect_temp[1])344if dist_temp < space_err:345# status transition scheduled, finish merging, '1' to '2'346s_merge_done.append(i)347# for the host robot having status of '2'348elif robots[i].status == 2:349# check if all key neighbors are still in range350neighbors_secured = True351for j in robots[i].key_neighbors:352if j == -1: continue # robots at two ends have only one key neighbor353if j not in index_list[i]:354neighbors_secured = False355break356if neighbors_secured == False:357# status transition scheduled, robot '2' gets lost, disassemble the group358s_line_lost.append(robots[i].group_id)359else:360# all the key neighbors are in goood position361# disassemble check, for all the '1' and '2'362status_list_temp = status_list[i][:]363index_list_temp = index_list[i][:]364# pop out the '0' first365while 0 in status_list_temp:366index_temp = status_list_temp.index(0)367status_list_temp.pop(index_temp)368index_list_temp.pop(index_temp)369# start the group attribution dictionary with first robot370if len(index_list_temp) > 0:371groups_temp = {robots[index_list_temp[0]].group_id: [index_list_temp[0]]}372for j in index_list_temp[1:]:373current_group = robots[j].group_id374if current_group in groups_temp.keys():375groups_temp[current_group].append(j)376else:377groups_temp[current_group] = [j]378# check if there are multiple groups detected379if len(groups_temp.keys()) > 1:380# status transition scheduled, to disassemble groups381s_disassemble.append(groups_temp.keys())382# for the host robot having status of '-1'383elif robots[i].status == -1:384# check if life time expires, and get status back to '0'385if robots[i].status_n1_life < 0:386s_back_0.append(i)387388# check 'groups' for any status change389for g_it in groups.keys():390if groups[g_it][4]: continue # already becoming dominant391if groups[g_it][0] > robot_quantity/2:392# the group has more than half the totoal number of robots393groups[g_it][4] = True # becoming dominant394groups[g_it][1] = 100.0 # a large number395if groups[g_it][1] < 0: # life time of a group expires396# schedule operation to disassemble this group397s_group_exp.append(g_it)398399# process the scheduled status change, in the order of the priority400# 1.s_grab_on, robot '0' grabs on robot '2', becoming '1'401# (this part of code is kind of redundant, needs to make it nicer)402for i in s_grab_on.keys():403it0 = s_grab_on[i] # 'it0' is the robot that robot 'i' tries to grab on404# discuss the merging availability of robot 'it0'405g_it = robots[it0].group_id # group id of 'it0'406# it0 was available when added to s_grab_on, but check again if occupied by others407# merge availability of smaller index side408side0_avail = robots[it0].status_2_avail1[0] & robots[it0].status_2_avail2[0]409side0_des = [-1,-1] # destination if merging at small index end410side0_hang = False # indicate if destination at side 0 is hanging411side0_next = -1 # next key neighbor expected to meet at this side412if side0_avail:413# calculate the merging destination414# if this side is beyond two ends of the line, it can only be the small end415if robots[it0].status_2_sequence == 0:416# the grab on robot is the robot of index 0 on the line417# get its only neighbor to calculate the line direction418it1 = robots[it0].key_neighbors[1] # second robot on the line419vect_temp = (robots[it0].pos[0] - robots[it1].pos[0],420robots[it0].pos[1] - robots[it1].pos[1]) # from it1 to it0421ori_temp = math.atan2(vect_temp[1], vect_temp[0])422side0_des = [robots[it0].pos[0] + line_space*math.cos(ori_temp),423robots[it0].pos[1] + line_space*math.sin(ori_temp)]424side0_hang = True425else:426# the grab on robot is not at the start of the line427# get its smaller index neighbor428it1 = robots[it0].key_neighbors[0]429side0_next = it1430# the destination is the middle position of it0 and it1431side0_des = [(robots[it0].pos[0]+robots[it1].pos[0])/2,432(robots[it0].pos[1]+robots[it1].pos[1])/2]433# merge availability of larger index side434side1_avail = robots[it0].status_2_avail1[1] & robots[it0].status_2_avail2[1]435side1_des = [-1,-1]436side1_hang = False # indicate if destination at side 1 is hanging437side1_next = -1 # next key neighbor expected to meet at this side438if side1_avail:439# calculate the merging destination440# if this side is beyond two ends of the line, it can only the large end441if robots[it0].status_2_end:442# the grab on robot is at the larger index end443# get its only neighbor to calculate the line direction444it1 = robots[it0].key_neighbors[0] # the inverse second on the line445vect_temp = (robots[it0].pos[0] - robots[it1].pos[0],446robots[it0].pos[1] - robots[it1].pos[1]) # from it1 to it0447ori_temp = math.atan2(vect_temp[1], vect_temp[0])448side1_des = [robots[it0].pos[0] + line_space*math.cos(ori_temp),449robots[it0].pos[1] + line_space*math.sin(ori_temp)]450side1_hang = True451else:452# the grab on robot is not at the larger index end453# get its larger index neighbor454it1 = robots[it0].key_neighbors[1]455side1_next = it1456# the destination is the middle position of it0 and it1457side1_des = [(robots[it0].pos[0]+robots[it1].pos[0])/2,458(robots[it0].pos[1]+robots[it1].pos[1])/2]459# check if there is at least one side is available460if side0_avail or side1_avail:461# perform operations regardless of which side to merge462robots[i].status = 1 # status becoming '1'463robots[i].group_id = g_it # assign the group id464robots[i].status_1_sub = 1465robots[i].key_neighbors = [-1, -1] # initialize with '-1's466groups[g_it][0] = groups[g_it][0] + 1 # increase the group size by 1467groups[g_it][1] = groups[g_it][1] + life_incre # increase life time468groups[g_it][3].append(i)469# deciding which side to merge470side_decision = -1 # 0 for left, 1 for right471if side0_avail and side1_avail:472# both sides are available, calculate the distance and compare473vect_temp = (side0_des[0]-robots[i].pos[0], side0_des[1]-robots[i].pos[1])474side0_dist = math.sqrt(vect_temp[0]*vect_temp[0] +475vect_temp[1]*vect_temp[1])476vect_temp = (side1_des[0]-robots[i].pos[0], side1_des[1]-robots[i].pos[1])477side1_dist = math.sqrt(vect_temp[0]*vect_temp[0] +478vect_temp[1]*vect_temp[1])479if side0_dist < side1_dist:480side_decision = 0481else:482side_decision = 1483elif side0_avail and (not side1_avail):484# only small index side is available, choose this side485side_decision = 0486elif side1_avail and (not side0_avail):487# only large index side is available, choose this side488side_decision = 1489# perform operations according the side decision490if side_decision == 0:491# operations of taking small index side492robots[i].key_neighbors[1] = it0 # has key neighbor at large index side493robots[i].status_1_1_des = side0_des494vect_temp = (side0_des[0]-robots[i].pos[0], side0_des[1]-robots[i].pos[1])495robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])496robots[it0].status_2_avail2[0] = False # reverse flag for small side497# robot 'it0' does not take 'i' as key nieghbor, only robots on the line498if not side0_hang:499it1 = side0_next500robots[i].key_neighbors[0] = it1501robots[it1].status_2_avail2[1] = False502else:503# operations of taking large index side504robots[i].key_neighbors[0] = it0 # has key neighbor at small index side505robots[i].status_1_1_des = side1_des506vect_temp = (side1_des[0]-robots[i].pos[0], side1_des[1]-robots[i].pos[1])507robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])508robots[it0].status_2_avail2[1] = False # reverse flag for large side509if not side1_hang:510it1 = side1_next511robots[i].key_neighbors[1] = it1512robots[it1].status_2_avail2[0] = False513# 2.s_init_form, robot '0' initial forms with another '0', becoming '1'514s_pair = [] # the container for finalized initial forming pairs515while len(s_init_form.keys()) != 0: # there are still robots to be processed516for i in s_init_form.keys():517it = s_init_form[i][0] # index temp518if it in s_init_form.keys():519if s_init_form[it][0] == i: # robot 'it' also recognizes 'i' as closest520s_pair.append([i, it])521s_init_form.pop(i) # pop out both 'i' and 'it'522s_init_form.pop(it)523break524elif i not in s_init_form[it]:525# usually should not be here unless robots have different sensing range526s_init_form[i].remove(it)527if len(s_init_form[i]) == 0:528s_init_form.pop(i)529break530# have not considered the situation that 'i' in 'it' but not first one531else:532# will be here if robot 'it' chooses to be bounced away by '1', or grab on '2'533s_init_form[i].remove(it)534if len(s_init_form[i]) == 0:535s_init_form.pop(i)536break537# process the finalized pairs538for pair in s_pair:539it0 = pair[0] # get indices of the pair540it1 = pair[1]541g_it = random.randint(0, group_id_upper_limit)542while g_it in groups.keys(): # keep generate until not duplicate543g_it = random.randint(0, group_id_upper_limit)544# update the 'robots' variable545robots[it0].status = 1546robots[it1].status = 1547robots[it0].group_id = g_it548robots[it1].group_id = g_it549robots[it0].status_1_sub = 0 # sub status '0' for initial forming550robots[it1].status_1_sub = 0551robots[it0].key_neighbors = [it1] # name the other as the key neighbor552robots[it1].key_neighbors = [it0]553# update the 'groups' variable554groups[g_it] = [2, 2*life_incre, [], [it0, it1], False] # add new entry555# deciding moving direction for the initial forming robots556vect_temp = (robots[it1].pos[0]-robots[it0].pos[0],557robots[it1].pos[1]-robots[it0].pos[1]) # pointing from it0 to it1558ori_temp = math.atan2(vect_temp[1], vect_temp[0])559if dist_table[it0][it1] > line_space: # equally dist_table[it1][it0]560# attracting each other, most likely this happens561robots[it0].ori = ori_temp562robots[it1].ori = reset_radian(ori_temp + math.pi)563else:564# repelling each other565robots[it0].ori = reset_radian(ori_temp + math.pi)566robots[it1].ori = ori_temp567# no need to check if they are already within the space error of line space568# this will be done in the next round of status change check569# 3.s_form_done, robot '1' finishes initial forming, becoming '2'570for g_it in s_form_done.keys():571if len(s_form_done[g_it]) == 2: # double check, both robots agree forming is done572it0 = s_form_done[g_it][0]573it1 = s_form_done[g_it][1]574# update 'robots' variable for 'it0' and 'it1'575robots[it0].status = 2576robots[it1].status = 2577# randomly deciding which is begining and end of the line, random choice578if random.random() > 0.5: # half chance579# swap 'ito' and 'it1', 'it0' as begining, 'it1' as end580temp = it0581it0 = it1582it1 = temp583# update the 'robots' variable584robots[it0].vel = 0 # give 0 speed temporarily585robots[it1].vel = 0586robots[it0].status_2_sequence = 0587robots[it1].status_2_sequence = 1588robots[it0].status_2_end = False589robots[it1].status_2_end = True590robots[it0].status_2_avail2 = [True, True] # both sides are available591robots[it1].status_2_avail2 = [True, True]592robots[it0].key_neighbors = [-1, it1] # one side has not key neighbor593robots[it1].key_neighbors = [it0, -1]594# update the 'groups' variable595g_it = robots[it0].group_id596groups[g_it][2] = [it0, it1] # add the robots for the line597groups[g_it][3] = [] # empty the forming & merging pool598# 4.s_merge_done, robot '1' finishes merging, becoming '2'599for i in s_merge_done:600# finding which side to merge601merge_check = -1602# '0' for merging at small index end of the line603# '1' for merging between two neighbors604# '2' for merging at large index end of the line605if robots[i].key_neighbors[0] == -1:606# only small index neighbor is empty607# large index side neighbor is at small index end of the line608merge_check = 0609else:610if robots[i].key_neighbors[1] == -1:611# only large index neighbor is empty612# small index side neighbor is at large index end of the line613merge_check = 2614else:615# both neighbors are present, merge in between616merge_check = 1617# perform the merge operations618robots[i].status = 2619robots[i].vel = 0 # give 0 speed temporarily620robots[i].status_2_avail2 = [True, True] # both sides have no merging robot621# no need to change key neighbors of robot 'i'622g_it = robots[i].group_id623groups[g_it][3].remove(i) # remove from the merging pool624if merge_check == 0: # merge into small index end of the line625it0 = robots[i].key_neighbors[1] # the old small index end626robots[i].status_2_sequence = 0627robots[i].status_2_end = False628groups[g_it][2].insert(0, i) # insert robot 'i' as first one629# shift sequence of robots starting from the second one630for j in groups[g_it][2][1:]:631robots[j].status_2_sequence = robots[j].status_2_sequence + 1632# update attributes of the second robot on the line633robots[it0].status_2_avail2[0] = True # becomes available again634robots[it0].key_neighbors[0] = i # add key neighbor at small index side635elif merge_check == 2: # merge into large index end of the line636it0 = robots[i].key_neighbors[0] # the old large index end637robots[i].status_2_sequence = robots[it0].status_2_sequence + 1638# no need to shift sequence of other robots on the line639robots[i].status_2_end = True640robots[it0].status_2_end = False # no longer the end of the line641robots[it0].status_2_avail2[1] = True642robots[it0].key_neighbors[1] = i # add key neighbor at large index side643groups[g_it][2].append(i) # append at the end of the list644elif merge_check == 1: # merge in the middle of two robots645# it0 is in small sequence, it1 is in large sequence646it0 = robots[i].key_neighbors[0]647it1 = robots[i].key_neighbors[1]648seq_new = robots[it1].status_2_sequence # 'i' will replace seq of 'it1'649robots[i].status_2_sequence = seq_new650robots[i].status_2_end = False651groups[g_it][2].insert(seq_new, i)652# shift sequence of robots starting form 'it1'653for j in groups[g_it][2][seq_new+1:]:654robots[j].status_2_sequence = robots[j].status_2_sequence + 1655# update attribution of it0 and it1656robots[it0].status_2_avail2[1] = True # large side of small index robot657robots[it0].key_neighbors[1] = i # replace with new member658robots[it1].status_2_avail2[0] = True # similar for large index robot659robots[it1].key_neighbors[0] = i660# # 5.s_form_lost, robot '1' gets lost during initial forming661# for g_it in s_form_lost:662# # disassemble the group together in s_disassemble663# s_disassemble.append[g_it]664# # 6.s_merge_lost, robot '1' gets lost during merging, becoming '-1'665# for i in s_merge_lost:666# robots[i].status = -1667# robots[i].vel = const_vel # restore the faster speed668# robots[i].ori = random.random() * 2*math.pi - math.pi669# robots[i].status_n1_life = random.randint(n1_life_lower, n1_life_upper)670# # update the key neighbors' status671# if robots[i].key_neighbors[0] != -1:672# # robot 'i' has a small index side key neighbor673# it0 = robots[i].key_neighbors[0]674# robots[it0].status_2_avail2[1] = True675# if robots[i].key_neighbors[1] != -1:676# # robot 'i' has a large index side key neighbor677# it1 = robots[i].key_neighbors[1]678# robots[it1].status_2_avail2[0] = True679# g_it = robots[i].group_id680# groups[g_it][0] = groups[g_it][0] - 1 # decrease group size by 1681# # do not decrease group's life time due to member lost682# groups[g_it][3].remove(i)683# 7.s_line_lost, robot '2' gets lost during adjusting on the line684for g_it in s_line_lost:685# disassemble the group together in s_disassemble686s_disassemble.append[g_it]687# 8.s_group_exp, natural life expiration of the groups688for g_it in s_group_exp:689s_disassemble.append([g_it]) # leave the work to s_disassemble690# 9.s_disassemble, mostly triggered by robot '1' or '2'691s_dis_list = [] # list of groups that have been finalized for disassembling692# compare number of members to decide which groups to disassemble693for gs_it in s_disassemble:694if len(gs_it) == 1:695# disassemble trigger from other sources696if gs_it[0] not in s_dis_list:697s_dis_list.append(gs_it[0])698else:699# compare which group has the most members, and disassemble the rest700g_temp = gs_it[:]701member_max = 0 # number of members in the group702group_max = -1 # corresponding group id with most members703for g_it in g_temp:704if groups[g_it][0] > member_max:705member_max = groups[g_it][0]706group_max = g_it707g_temp.remove(group_max) # remove the group with the most members708for g_it in g_temp:709if g_it not in s_dis_list:710s_dis_list.append(g_it)711# start disassembling712for g_it in s_dis_list:713# update the 'robots' variable714for i in groups[g_it][3]: # for robots off the line715robots[i].vel = const_vel # restore the faster speed716robots[i].status = -1717robots[i].status_n1_life = random.randint(n1_life_lower, n1_life_upper)718robots[i].ori = random.random() * 2*math.pi - math.pi719# same way of exploding the robots on the line720num_online = len(groups[g_it][2]) # number of robots on the line721num_1_half = num_online/2 # number of robots for the first half722num_2_half = num_online - num_1_half # for the second half723for i in groups[g_it][2]: # for robots on the line724robots[i].vel = const_vel # restore the faster speed725robots[i].status = -1726robots[i].status_n1_life = random.randint(n1_life_lower, n1_life_upper)727ori_temp = 0 # orientation of the line, calculate individually728seq_temp = robots[i].status_2_sequence # sequence on the line729if robots[i].status_2_sequence == 0:730# then get second one on the line731it0 = groups[robots[i].group_id][2][1]732# orientation points to the small index end733ori_temp = math.atan2(robots[i].pos[1]-robots[it0].pos[1],734robots[i].pos[0]-robots[it0].pos[0])735elif robots[i].status_2_end == True:736# then get inverse second one on the line737it0 = groups[robots[i].group_id][2][-2]738# orientation points to the large index end739ori_temp = math.atan2(robots[i].pos[1]-robots[it0].pos[1],740robots[i].pos[0]-robots[it0].pos[0])741else:742# get both neighbors743it0 = groups[g_it][2][seq_temp - 1]744it1 = groups[g_it][2][seq_temp + 1]745if seq_temp < num_1_half: # robot in the first half746# orientation points to the small index end747ori_temp = math.atan2(robots[it0].pos[1]-robots[it1].pos[1],748robots[it0].pos[0]-robots[it1].pos[0])749else:750# orientation points to the large index end751ori_temp = math.atan2(robots[it1].pos[1]-robots[it0].pos[1],752robots[it1].pos[0]-robots[it0].pos[0])753# then calculate the 'explosion' direction754if seq_temp < num_1_half:755robots[i].ori = reset_radian(ori_temp + math.pi*(seq_temp+1)/(num_1_half+1))756# always 'explode' to the left side757else:758robots[i].ori = reset_radian(ori_temp + math.pi*(num_online-seq_temp)/(num_2_half+1))759# pop out this group from 'groups'760groups.pop(g_it)761# 10.s_back_0, life time of robot '-1' expires, becoming '0'762for i in s_back_0:763# still maintaining the old moving direction and velocity764robots[i].status = 0765766# update the physics(pos, vel and ori), and wall bouncing, life decrese of '-1'767for i in range(robot_quantity):768# check if out of boundaries, same algorithm from previous line formation program769# change only direction of velocity770if robots[i].pos[0] >= world_size[0]: # out of right boundary771if math.cos(robots[i].ori) > 0: # velocity on x is pointing right772robots[i].ori = reset_radian(2*(math.pi/2) - robots[i].ori)773elif robots[i].pos[0] <= 0: # out of left boundary774if math.cos(robots[i].ori) < 0: # velocity on x is pointing left775robots[i].ori = reset_radian(2*(math.pi/2) - robots[i].ori)776if robots[i].pos[1] >= world_size[1]: # out of top boundary777if math.sin(robots[i].ori) > 0: # velocity on y is pointing up778robots[i].ori = reset_radian(2*(0) - robots[i].ori)779elif robots[i].pos[1] <= 0: # out of bottom boundary780if math.sin(robots[i].ori) < 0: # velocity on y is pointing down781robots[i].ori = reset_radian(2*(0) - robots[i].ori)782# update one step of distance783travel_dist = robots[i].vel * frame_period/1000.0784robots[i].pos[0] = robots[i].pos[0] + travel_dist*math.cos(robots[i].ori)785robots[i].pos[1] = robots[i].pos[1] + travel_dist*math.sin(robots[i].ori)786# update moving direciton and destination of robot '1'787if robots[i].status == 1:788if robots[i].status_1_sub == 0: # for the initial forming robots789it0 = robots[i].key_neighbors[0]790vect_temp = (robots[it0].pos[0]-robots[i].pos[0],791robots[it0].pos[1]-robots[i].pos[1])792ori_temp = math.atan2(vect_temp[1], vect_temp[0])793if dist_table[i][it0] > line_space:794robots[i].ori = ori_temp795else:796robots[i].ori = reset_radian(ori_temp + math.pi)797else: # for the merging robot798# the merging des should be updated, because robots '2' are also moving799if (robots[i].key_neighbors[0] == -1 or800robots[i].key_neighbors[1] == -1):801# robot 'i' is merging at starting or end of the line802it0 = 0 # represent the robot at the end803it1 = 0 # represent the robot inward of it0804if robots[i].key_neighbors[0] == -1:805it0 = robots[i].key_neighbors[1]806it1 = robots[it0].key_neighbors[1]807else:808it0 = robots[i].key_neighbors[0]809it1 = robots[it0].key_neighbors[0]810# calculate the new destination and new orientation to the destination811vect_temp = (robots[it0].pos[0]-robots[it1].pos[0],812robots[it0].pos[1]-robots[it1].pos[1]) # from it1 to it0813ori_temp = math.atan2(vect_temp[1], vect_temp[0])814des_new = [robots[it0].pos[0] + line_space*math.cos(ori_temp),815robots[it0].pos[1] + line_space*math.sin(ori_temp)]816# update the new destination817robots[i].status_1_1_des = des_new[:]818vect_temp = (des_new[0]-robots[i].pos[0],819des_new[1]-robots[i].pos[1]) # from robot 'i' to des820# update the new orientation821robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])822else:823# robot 'i' is merging in between824it0 = robots[i].key_neighbors[0]825it1 = robots[i].key_neighbors[1]826des_new = [(robots[it0].pos[0]+robots[it1].pos[0])/2,827(robots[it0].pos[1]+robots[it1].pos[1])/2]828robots[i].status_1_1_des = des_new[:]829vect_temp = (des_new[0]-robots[i].pos[0],830des_new[1]-robots[i].pos[1]) # from robot 'i' to des831# update the new orientation832robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])833# update moving direction, velocity and first merge availability of robot '2'834elif robots[i].status == 2:835if robots[i].status_2_sequence == 0:836# robot 'i' is at the small index end837it1 = robots[i].key_neighbors[1] # use it1 because on the large side838# update the first merge availability839if dist_table[i][it1] > merge_min_dist:840robots[i].status_2_avail1[1] = True841else:842robots[i].status_2_avail1[1] = False843# update the moving direction and velocity844# for adjusting on the line, just moving closer or farther to it1845vect_temp = [robots[i].pos[0]-robots[it1].pos[0] ,846robots[i].pos[1]-robots[it1].pos[1]] # from it1 to i847ori_temp = math.atan2(vect_temp[1], vect_temp[0])848if dist_table[i][it1] > line_space:849# too far away, move closer to it1850robots[i].ori = reset_radian(ori_temp + math.pi)851robots[i].vel = (dist_table[i][it1] - line_space) * adjust_vel_coef852else:853# too close, move farther away from it1854robots[i].ori = ori_temp855robots[i].vel = (line_space - dist_table[i][it1]) * adjust_vel_coef856elif robots[i].status_2_end:857# robot 'i' is at the large index end858it0 = robots[i].key_neighbors[0]859# update the first merge availability860if dist_table[i][it0] > merge_min_dist:861robots[i].status_2_avail1[0] = True862else:863robots[i].status_2_avail1[0] = True864# update the moving direction and velocity865vect_temp = [robots[i].pos[0]-robots[it0].pos[0],866robots[i].pos[1]-robots[it0].pos[1]] # from it0 to i867ori_temp = math.atan2(vect_temp[1], vect_temp[0])868if dist_table[i][it0] > line_space:869# too far away, move closer to it0870robots[i].ori = reset_radian(ori_temp + math.pi/2)871robots[i].vel = (dist_table[i][it0] - line_space) * adjust_vel_coef872else:873# too close, move farther away from it0874robots[i].ori = ori_temp875robots[i].vel = (line_space - dist_table[i][it0]) * adjust_vel_coef876else:877# robot 'i' is at no end878it0 = robots[i].key_neighbors[0]879it1 = robots[i].key_neighbors[1]880# update the first merge availability881if dist_table[i][it0] > merge_min_dist:882robots[i].status_2_avail1[0] = True883else:884robots[i].status_2_avail1[0] = False885if dist_table[i][it1] > merge_min_dist:886robots[i].status_2_avail1[1] = True887else:888robots[i].status_2_avail1[1] = False889# update the moving direction and velocity890des_new = [(robots[it0].pos[0]+robots[it1].pos[0])/2,891(robots[it0].pos[1]+robots[it1].pos[1])/2]892vect_temp = (des_new[0]-robots[i].pos[0],893des_new[1]-robots[i].pos[1]) # from robot 'i' to des894dist_temp = math.sqrt(vect_temp[0]*vect_temp[0] +895vect_temp[1]*vect_temp[1])896robots[i].ori = math.atan2(vect_temp[1], vect_temp[0])897robots[i].vel = dist_temp * adjust_vel_coef898# decrease life time of robot '-1'899elif robots[i].status == -1:900robots[i].status_n1_life = robots[i].status_n1_life - frame_period/1000.0901# life time decrease of the groups902for g_it in groups.keys():903if groups[g_it][4]: continue # not decrease life of the dominant904groups[g_it][1] = groups[g_it][1] - frame_period/1000.0905906# graphics update907screen.fill(background_color)908# draw the robots909for i in range(robot_quantity):910display_pos = world_to_display(robots[i].pos, world_size, screen_size)911# get color of the robot912color_temp = ()913if robots[i].status == 0:914color_temp = robot_0_color915elif robots[i].status == 1:916color_temp = robot_1_color917elif robots[i].status == 2:918color_temp = robot_2_color919elif robots[i].status == -1:920color_temp = robot_n1_color921# draw the robot as a small solid circle922pygame.draw.circle(screen, color_temp, display_pos, robot_size, 0) # fill the circle923pygame.display.update()924925pygame.quit()926927928929930