def iteration_connections(solution): """ Iteration of the hillclimber that swithces out connections within routes. A random route is chosen, then with a certain probability either the first or last connection is removed. With a certain probability a new connection is placed at the location of the first or last connection. This happens independently. If the score improves, the change is accepted. Args: solution: An instance of the solution class. old_score: The score of the solution Returns: The index of the new route, and the new route itself. """ # Choose random route. route_index = rd.randint(0, solution.max_trains - 1) old_route = solution.route_list[route_index] new_route = rt.Route(list(old_route.connection_list)) # Determine which end is cut of, if any. new_route = cut_route_ends(new_route) new_route = add_new_endpoint(new_route, solution) # Return proposed changes. return route_index, new_route
def create_random_route(solution): """ Create a random route. Args: solution: An instance of the solution class. Returns: The route that we created. """ connection_list = [] route = rt.Route(connection_list) # Choose begin station. begin_station = rd.choice(solution.station_dict_key_list) # Create a new route. while True: route_time = route.time() weight = 0.1 * route_time / solution.max_minutes if 0.05 + weight > rd.random(): break end_station = rd.choice(solution.station_dict[begin_station].neighbors) if route_time + end_station[1] < solution.max_minutes: route.append_route(begin_station, end_station[0], end_station[1]) begin_station = end_station[0] else: break return route
def greedy(solution): """ Greedy algorithm that hopes to fin the perfect solutioinself. Args: solution: An instance of the solution class, possibly containing routes. Returns; The solution: a combination of routes. """ connection_archive = set() for i in range(solution.max_trains): connection_list = [] route = rt.Route(connection_list) # Find best begin station. begin_station, end_station, travel_time, best_end_station_index, found_another_station = find_best_begin_station( solution, connection_archive) # Quit if no solution was found. if not found_another_station: # Ensure our solution will contain maximum ammount of trains. for _ in range(solution.max_trains - i): solution.route_list.append(rt.Route([])) return solution append_to_connection_archive(connection_archive, begin_station, end_station) connection = { "begin": begin_station, "end": end_station, "time": travel_time } # Add new step to route. connection_list.append(connection) route.connection_list = connection_list # Look for the best, closest, critical route. find_new_connection(begin_station, end_station, solution, connection_archive, route) # Add newly created route to route_list. solution.route_list.append(route) return solution
def naive(nodes, capacity, algorithm='default'): depot = nodes[0] # initial state clients_to_visit = [node.id for node in nodes[1:]] routes = [] while len(clients_to_visit) > 0: # create a truck to visit nodes that have not been visited before truck_capacity = capacity truck_position = 0 # create empty route starting in 0 route = Route([0], 0) # truck looping while True: # choose next destination (next client or depot) clients_buffer = deepcopy(clients_to_visit) while len(clients_buffer) > 0: # pick random element of bufffer random_index = random.randint(0, len(clients_buffer) - 1) candidate_id = clients_buffer[random_index] clients_buffer.remove(candidate_id) candidate_node = nodes[candidate_id] candidate_cost = nodes[truck_position].distance_to_node( candidate_node.x, candidate_node.y) if candidate_node.demand <= truck_capacity: # this is a good candidate, it will be the next node next_node = nodes[candidate_id] cost_to_next_node = candidate_cost break else: # if truck capacity is not enough for any client, return to depot next_node = depot cost_to_next_node = nodes[truck_position].distance_to_node( depot.x, depot.y) # go to next node and update state route.path.append(next_node.id) route.cost += cost_to_next_node truck_capacity -= next_node.demand truck_position = next_node.id if next_node.id in clients_to_visit: clients_to_visit.remove(next_node.id) # stop truck looping when truck has returned to depot if truck_position == depot.id: break # store route data routes.append(route) # format output according to algorithm if algorithm == 'annealing': return format_to_annealing(routes) elif algorithm == 'local_search': return format_to_local_search(routes) else: return routes
def two_opt_single(route: Route): arc_list = route.required_arc_list # extract sub-route indices idx = sample(range(len(arc_list)), 2) if idx[0] < idx[1]: start, end = idx else: end, start = idx # create a new route new_route = Route() new_sub_list = deepcopy(arc_list[start: end + 1]) for arc in new_sub_list: arc[1] = not arc[1] new_sub_list.reverse() new_arc_list = arc_list[: start] + new_sub_list + arc_list[end + 1:] new_route.required_arc_list = new_arc_list new_route.cost = get_route_cost(new_arc_list) new_route.load = route.load return new_route
def initialize_columns(instance): """ Se inicializan las columnas: cada cliente es atendido exclusivamente por un vehículo con la capacidad mínima entre todos los vehículos que pueden satisfacerlo. """ routes = [] clients = set(instance.demand) clients.remove(0) for i in clients: # Entre los vehículos que pueden satisface la demanda de i, elegimos al de menor capacidad k = min((v for v, c in instance.cap.items() if c >= instance.demand[i]), key=lambda v: instance.cap[v]) path = Path(k) path.add_node(i, instance) routes.append(Route.from_path(path, instance)) return routes
def hillclimber(solution, route_iterations=10000, connection_iterations=0): """ Hillclimber algortihm that tries to find the optimal set of routes, A.K.A. an optimal solution. This implementation of a hillclimber contains two different iterations, one based on replacing routes and one based on replacing only the beginning and ending of a route. Args: solution: An instance of the solution class, possibly containing routes. route_iterations: The number of route iterations, default is 10000. connection_iterations: The number of connection iterations, default is 0. Returns: The solution. """ # Fill solution with empty routes if the route list is empty. if solution.route_list == []: for i in range(solution.max_trains): route = rt.Route([]) solution.route_list.append(route) score = solution.score() scores_array = [] # Perform the route iteration the specified amount of times. for _ in range(int(route_iterations / 100)): for _ in range(100): route_index, new_route = iteration_routes(solution) score, solution = check_for_improvement(score, solution, route_index, new_route) scores_array.append(solution.score()) # Perform the connection iteration the specified amount of times. for _ in range(int(connection_iterations / 100)): for _ in range(100): route_index, new_route = iteration_connections(solution) score, solution = check_for_improvement(score, solution, route_index, new_route) scores_array.append(solution.score()) return solution, scores_array
def path_scanning(free: list): route = Route() i = DEPOT discount = config.discount while free: _d = np.inf _u = -1 reverse = False length = len(free) for u in range(length): if route.load + free[u][IDX_DEMAND] > discount * CAPACITY: continue if DIST[i, free[u][IDX_BEGIN]] < _d: _d = DIST[i, free[u][IDX_BEGIN]] _u = u reverse = False elif DIST[i, free[u][IDX_END]] < _d: _d = DIST[i, free[u][IDX_END]] _u = u reverse = True elif getrandbits(1): if DIST[i, free[u][IDX_BEGIN]] == _d: _u = u reverse = False elif DIST[i, free[u][IDX_END]] == _d: _u = u reverse = True if _u != -1: edge = free[_u] if not reverse: route.required_arc_list.append([edge, False]) i = edge[IDX_END] else: route.required_arc_list.append([edge, True]) i = edge[IDX_BEGIN] route.load += edge[IDX_DEMAND] route.cost += _d + edge[IDX_COST] del free[_u] else: break route.cost += DIST[i, DEPOT] return route
def create_random_solution(solution): """ Finds a set of random routes. Args: solution: An instance of the solution class, with empty route list. Returns: A randomly egenerated solution. """ # Determine how many trains can travel. number_of_trains = rd.randint(int(solution.max_trains - \ math.sqrt(solution.max_trains)), solution.max_trains) # Ensure our solution will contain maximum ammount of trains. for _ in range(solution.max_trains - number_of_trains): solution.route_list.append(rt.Route([])) # Create random routes. for _ in range(number_of_trains): route = helper.create_random_route(solution) solution.route_list.append(route) return solution
def greedy(nodes, capacity, algorithm='default'): depot = nodes[0] # initial state clients_to_visit = [node.id for node in nodes[1:]] routes = [] while len(clients_to_visit) > 0: # create a truck to visit nodes that have not been visited before truck_capacity = capacity truck_position = 0 # create empty route starting in 0 route = Route([0], 0) # truck looping while True: # calculate costs costs = {} for client_id in clients_to_visit: client = nodes[client_id] cost = nodes[truck_position].distance_to_node( client.x, client.y) costs[client_id] = cost # sort possibilities from smallest to biggest cost, [(client_id, cost)] costs = sorted(costs.items(), key=lambda kv: (kv[1], kv[0])) # choose next destination (next client or depot) while len(costs) > 0: # pick first element of list candidate_id, candidate_cost = costs[0] costs = costs[1:] if nodes[candidate_id].demand <= truck_capacity: # this is a good candidate, it will be the next node next_node = nodes[candidate_id] cost_to_next_node = candidate_cost break else: # if truck capacity is not enough for any client, return to depot next_node = depot cost_to_next_node = nodes[truck_position].distance_to_node( depot.x, depot.y) # go to next node and update state route.path.append(next_node.id) route.cost += cost_to_next_node truck_capacity -= next_node.demand truck_position = next_node.id if next_node.id in clients_to_visit: clients_to_visit.remove(next_node.id) # stop truck looping when truck has returned to depot if truck_position == depot.id: break # store route data routes.append(route) # format output according to algorithm if algorithm == 'annealing': return format_to_annealing(routes) elif algorithm == 'local_search': return format_to_local_search(routes) else: return routes
def two_opt_double(route1: Route, route2: Route): arc_list1 = route1.required_arc_list arc_list2 = route2.required_arc_list idx1 = (len(arc_list1) - 1) // 2 idx2 = (len(arc_list2) - 1) // 2 half11 = arc_list1[:idx1] half12 = arc_list1[idx1:] half21 = arc_list2[:idx2] half22 = arc_list2[idx2:] load11 = 0 for arc in half11: load11 += arc[0][IDX_DEMAND] load12 = route1.load - load11 load21 = 0 for arc in half21: load21 += arc[0][IDX_DEMAND] load22 = route2.load - load21 l1 = load11 + load22 l2 = load12 + load21 l3 = load11 + load21 l4 = load12 + load22 new_route1 = new_route2 = new_route3 = new_route4 = None if l1 < CAPACITY and l2 < CAPACITY: new_route1 = Route() new_route1.required_arc_list = half11 + half22 new_route1.load = l1 new_route1.cost = get_route_cost(new_route1.required_arc_list) new_route2 = Route() new_route2.required_arc_list = half21 + half12 new_route2.load = l2 new_route2.cost = get_route_cost(new_route2.required_arc_list) if l3 < CAPACITY and l4 < CAPACITY: new_route3 = Route() reverse21 = deepcopy(half21) for arc in reverse21: arc[1] = not arc[1] reverse21.reverse() new_route3.required_arc_list = half11 + reverse21 new_route3.load = l3 new_route3.cost = get_route_cost(new_route3.required_arc_list) new_route4 = Route() reverse12 = deepcopy(half12) for arc in reverse12: arc[1] = not arc[1] reverse12.reverse() new_route4.required_arc_list = reverse12 + half22 new_route4.load = l4 new_route4.cost = get_route_cost(new_route4.required_arc_list) if new_route1 is None and new_route3 is None: return None, None elif new_route1 is None: return new_route3, new_route4 elif new_route3 is None: return new_route1, new_route2 else: if new_route1.cost + new_route2.cost < new_route3.cost + new_route4.cost: return new_route1, new_route2 else: return new_route3, new_route4
def swap(solution): route_list = solution.route_list route_list_length = len(route_list) route_idx_1 = randrange(0, route_list_length) route_1 = route_list[route_idx_1] route_idx_2 = randrange(0, route_list_length - 1) if route_idx_2 >= route_idx_1: route_idx_2 += 1 route_2 = route_list[route_idx_2] arc_list_1 = route_1.required_arc_list arc_list_2 = route_2.required_arc_list arc_idx_1 = randrange(0, len(arc_list_1)) load_1 = route_1.load load_2 = route_2.load arc_1 = arc_list_1[arc_idx_1] demand_1 = arc_1[0][IDX_DEMAND] flag = False arc_2 = arc_idx_2 = new_load_1 = new_load_2 = None for arc_idx_2 in range(0, len(arc_list_2)): arc_2 = arc_list_2[arc_idx_2] demand_2 = arc_2[0][IDX_DEMAND] new_load_1 = load_1 + demand_2 - demand_1 new_load_2 = load_2 + demand_1 - demand_2 if new_load_1 <= CAPACITY and new_load_2 <= CAPACITY: flag = True break # swapping two routes is better than swapping one route if flag: new_arc_list_11 = arc_list_1[:] new_arc_list_12 = arc_list_1[:] new_arc_list_21 = arc_list_2[:] new_arc_list_22 = arc_list_2[:] new_arc_list_11[arc_idx_1] = arc_2 new_arc_list_12[arc_idx_1] = [arc_2[0], not arc_2[1]] new_arc_list_21[arc_idx_2] = arc_1 new_arc_list_22[arc_idx_2] = [arc_1[0], not arc_1[1]] cost11 = get_route_cost(new_arc_list_11) cost12 = get_route_cost(new_arc_list_12) cost21 = get_route_cost(new_arc_list_21) cost22 = get_route_cost(new_arc_list_22) new_route_1 = Route() new_route_2 = Route() new_route_1.load = new_load_1 new_route_2.load = new_load_2 if cost11 < cost12: new_route_1.required_arc_list = new_arc_list_11 new_route_1.cost = cost11 else: new_route_1.required_arc_list = new_arc_list_12 new_route_1.cost = cost12 if cost21 < cost22: new_route_2.required_arc_list = new_arc_list_21 new_route_2.cost = cost21 else: new_route_2.required_arc_list = new_arc_list_22 new_route_2.cost = cost22 new_route_list = route_list[:] new_route_list[route_idx_1] = new_route_1 new_route_list[route_idx_2] = new_route_2 new_solution = Solution() new_solution.route_list = new_route_list new_solution.quality = solution.quality + new_route_1.cost - route_1.cost + new_route_2.cost - route_2.cost return new_solution else: new_route = Route() new_route.load = route_1.load arc_list_1_length = len(arc_list_1) if arc_list_1_length < 2: return solution arc_idx_2 = randrange(0, arc_list_1_length - 1) if arc_idx_2 >= arc_idx_1: arc_idx_2 += 1 arc_2 = arc_list_1[arc_idx_2] new_arc_list_1 = arc_list_1[:] new_arc_list_2 = arc_list_1[:] new_arc_list_3 = arc_list_1[:] new_arc_list_4 = arc_list_1[:] new_arc_list_1[arc_idx_1] = arc_2 new_arc_list_1[arc_idx_2] = arc_1 new_arc_list_2[arc_idx_1] = [arc_2[0], not arc_2[1]] new_arc_list_2[arc_idx_2] = arc_1 new_arc_list_3[arc_idx_1] = arc_2 new_arc_list_3[arc_idx_2] = [arc_1[0], not arc_1[1]] new_arc_list_4[arc_idx_1] = [arc_2[0], not arc_2[1]] new_arc_list_4[arc_idx_2] = [arc_1[0], not arc_1[1]] cost1 = get_route_cost(new_arc_list_1) cost2 = get_route_cost(new_arc_list_2) cost3 = get_route_cost(new_arc_list_3) cost4 = get_route_cost(new_arc_list_4) min_cost = min(cost1, cost2, cost3, cost4) new_route.cost = min_cost if cost1 == min_cost: new_route.required_arc_list = new_arc_list_1 elif cost2 == min_cost: new_route.required_arc_list = new_arc_list_2 elif cost3 == min_cost: new_route.required_arc_list = new_arc_list_3 else: new_route.required_arc_list = new_arc_list_4 new_solution = Solution() new_route_list = route_list[:] new_route_list[route_idx_1] = new_route new_solution.route_list = new_route_list new_solution.quality = solution.quality + new_route.cost - route_1.cost return new_solution
def single_insertion(solution: Solution): route_list = solution.route_list remove_route_idx = randrange(0, len(route_list)) remove_route = route_list[remove_route_idx] remove_arc_list = remove_route.required_arc_list remove_arc_idx = randrange(0, len(remove_arc_list)) remove_arc = remove_arc_list[remove_arc_idx] demand = remove_arc[0][IDX_DEMAND] insert_route_idx = None for i in range(len(route_list)): if i != remove_route_idx and route_list[i].load + demand < CAPACITY: insert_route_idx = i break if insert_route_idx is not None: insert_route = route_list[insert_route_idx] insert_arc_list = insert_route.required_arc_list insert_arc_idx = randrange(0, len(insert_arc_list) + 1) insert_arc = remove_arc new_insert_route = Route() new_insert_arc_list_1 = insert_arc_list[:] new_insert_arc_list_2 = insert_arc_list[:] new_insert_arc_list_1.insert(insert_arc_idx, insert_arc) new_insert_arc_list_2.insert(insert_arc_idx, [insert_arc[0], not insert_arc[1]]) cost1 = get_route_cost(new_insert_arc_list_1) cost2 = get_route_cost(new_insert_arc_list_2) if cost1 < cost2: new_insert_route.required_arc_list = new_insert_arc_list_1 new_insert_route.cost = cost1 else: new_insert_route.required_arc_list = new_insert_arc_list_2 new_insert_route.cost = cost2 new_insert_route.load = insert_route.load + demand new_route_list = route_list[:] new_remove_arc_list = remove_arc_list[:] new_remove_route = None del new_remove_arc_list[remove_arc_idx] # update insert route first new_route_list[insert_route_idx] = new_insert_route if not new_remove_arc_list: del new_route_list[remove_route_idx] else: new_remove_route = Route() new_remove_route.required_arc_list = new_remove_arc_list new_remove_route.load = remove_route.load - demand new_remove_route.cost = get_route_cost(new_remove_arc_list) new_route_list[remove_route_idx] = new_remove_route diff1 = (0 if new_remove_route is None else new_remove_route.cost) - remove_route.cost diff2 = new_insert_route.cost - insert_route.cost # print('cost1', diff1 + diff2) new_solution = Solution() new_solution.route_list = new_route_list new_solution.quality = solution.quality + diff1 + diff2 # print(new_solution.quality, get_quality(new_route_list)) return new_solution else: length = len(remove_arc_list) if length < 2: return solution insert_arc_idx = randrange(0, length - 1) if insert_arc_idx >= remove_arc_idx: insert_arc_idx += 1 new_route = Route() new_route.load = remove_route.load arc_list_1 = remove_arc_list[:] arc_list_2 = remove_arc_list[:] arc_list_1.insert(insert_arc_idx, arc_list_1.pop(remove_arc_idx)) del arc_list_2[remove_arc_idx] arc_list_2.insert(insert_arc_idx, [remove_arc[0], not remove_arc[1]]) cost1 = get_route_cost(arc_list_1) cost2 = get_route_cost(arc_list_2) if cost1 < cost2: new_route.required_arc_list = arc_list_1 new_route.cost = cost1 else: new_route.required_arc_list = arc_list_2 new_route.cost = cost2 # new_route.required_arc_list = arc_list new_route_list = route_list[:] new_route_list[remove_route_idx] = new_route new_solution = Solution() new_solution.route_list = new_route_list diff = new_route.cost - remove_route.cost # print('cost2', diff) new_solution.quality = solution.quality + diff # print(new_solution.quality, get_quality(new_route_list)) return new_solution
def solve_SPk(pi, lamb, k, instance): """ Se resulve el problema \bar{SP} restringido al tipo de vehículo k. Se utiliza el procedimiento de etiquetas detallada en la tercera sección del informe. :param pi: diccionario con los valores óptimos de las variables del dual correspondiente al primer conjunto de restricciones de CLP :type pi: dict :param lamb: diccionario con los valores óptimos de las variables del dual correspondiente al primer conjunto de restricciones de CLP :type lamb: dict :param k: tipo de vehículo :type k: int :param instance: datos del problema :type instance: Instance :return: ruta con menor costo reducido para el tipo de vehículo k y su costo reducido :rtype: Route, float """ pi[0] = lamb[k] bk = instance.cap[k] # Removemos a los clientes cuya demanda supera a bk nodes = set(filter(lambda c: instance.demand[c] <= bk, instance.demand)) clients = nodes.difference({0}) # Se define la matriz de costos del subgrafo \bar{G}_k cost_matrix = make_cost_matrix(pi, k, instance) # Se inicializa el estado del depósito warehouse_label = Label.warehouse_label() # Se crean los conjuntos de estados cuya cota superior coincide con su cota inferior P = {j: [] for j in clients} P[0] = [warehouse_label] L = deque([warehouse_label]) while L: label = L.popleft() for j in filter(lambda c: label.q + instance.demand[c] <= bk and c != label.pred and c != label.node, clients): new_label = Label(label.q + instance.demand[j], j) new_label.path = label.path + [j] new_label.pred = label.node new_label.reduced_cost = get_reduced_cost(new_label, cost_matrix, instance, k) dominates = [] dominated = False # Se chequea si la nueva etiqueta no está dominada for s in P[j]: if s.reduced_cost > new_label.reduced_cost: dominates.append(s) elif s.reduced_cost < new_label.reduced_cost: dominated = True break if not dominated: # Se encola la nueva etiqueta L.append(new_label) P[j].append(new_label) # Se eliminan todas las etiquetas dominadas por la nueva for s in dominates: P[j].remove(s) try: L.remove(s) except ValueError: pass best_state = min((s for s in chain.from_iterable(P.values())), key=lambda s: getattr(s, 'reduced_cost')) best_path = Path.from_sequence(best_state.path, instance, k) return Route.from_path(best_path, instance), get_reduced_cost(best_state, cost_matrix, instance, k)
def pulling_algorithm(pi, lamb, k, instance): """ Método para resolver \bar{SP} restringido al tipo de vehículo k desarrollado por Desrochers. No utilizado para resolver las instancias. :param pi: diccionario con los valores óptimos de las variables del dual correspondiente al primer conjunto de restricciones de CLP :type pi: dict :param lamb: diccionario con los valores óptimos de las variables del dual correspondiente al primer conjunto de restricciones de CLP :type lamb: dict :param k: tipo de vehículo :type k: int :param instance: datos del problema :type instance: Instance :return: ruta con menor costo reducido para el tipo de vehículo k y su costo reducido :rtype: Route, float """ pi[0] = lamb[k] bk = instance.cap[k] # Removemos a los clientes cuya demanda supera a bk nodes = set(filter(lambda c: instance.demand[c] <= bk, instance.demand)) clients = nodes.difference({0}) # Se define la matriz de costos del subgrafo \bar{G}_k cost_matrix = make_cost_matrix(pi, k, instance) # Se inicilizan los estados de los nodos correspondientes a los clientes states = [State(q, j) for j in clients for q in range(instance.demand[j], bk + 1)] # Se inicializa el estado del depósito depo_state = State.warehouse_state() states.append(depo_state) # Se crean los conjuntos de estados cuya cota superior coincide con su cota inferior P = {j: [] for j in clients} P[0] = [depo_state] # Consideramos el conjunto W de estados cuyas cotas no coinciden W = deque(sorted([s for s in states if s.ub != s.lb], key=lambda s: (s.q, s.node))) best_state = None best_rc = 1e6 while W: # Entre los estados de cada j cuya cotas no coinciden, elegimos el que tiene menor q. Ante empates, elegimos el # que tiene menor j state = W.popleft() # Actualizamos la cota superior del estado. Primero calculamos el estado previo a (q,j) para el cual se realiza # el mínimo demand_bound = state.q - instance.demand[state.node] candidate_states = [s for s in states if s.node != state.node and s.q <= demand_bound] candidate_phis = [phi(s, state.node, cost_matrix) for s in candidate_states] prev_state, prev_phi = get_prev_state(candidate_states, candidate_phis) state.pred = prev_state.node state.ub = prev_phi state.path = prev_state.path + [state.node] # Se actualiza la cota superior del segundo mejor camino. Si el conjunto sobre el que se toma el mínimo es # vacío, la cota no se altera state.sub = get_secondary_ub(candidate_states, candidate_phis, state.pred) # Se actualiza la cota inferior del estado state.lb = min(map(lambda s: s.lb + cost_matrix[s.node][state.node], candidate_states)) # Actualizamos Pj de ser necesario: if isclose(state.lb, state.ub): P[state.node].append(state) state_rc = get_reduced_cost(state, cost_matrix, instance, k) if state_rc < best_rc: best_rc = state_rc best_state = state best_path = Path.from_sequence(best_state.path, instance, k) return Route.from_path(best_path, instance), get_reduced_cost(best_state, cost_matrix, instance, k)
from classes import Tile, Route from classes import Side, SideType, EndPoints import json start_tile = Tile() start_tile.id = 1 start_tile.order = 1 start_tile.image = "base64Image" start_tile.cloister = False start_tile.sides[Side.North] = SideType.City start_tile.sides[Side.East] = SideType.Road start_tile.sides[Side.South] = SideType.Farm start_tile.sides[Side.West] = SideType.Road newRoute = Route() newRoute.rid = "1-1" newRoute.route_type = SideType.City newRoute.endpoints.append(EndPoints.TopLeft) newRoute.endpoints.append(EndPoints.TopMiddle) newRoute.endpoints.append(EndPoints.TopRight) newRoute.meeple_pos = (100, 100) start_tile.routes.append(newRoute) newRoute = Route() newRoute.rid = "1-2" newRoute.route_type = SideType.Road newRoute.endpoints.append(EndPoints.LeftMiddle) newRoute.endpoints.append(EndPoints.RightMiddle) newRoute.meeple_pos = (200, 200) start_tile.routes.append(newRoute)