def founding_order(the_line, groups, debug=False): results = order_block.default_line_results(the_line) all_cities = the_line.the_world.cities() city_dict = the_line.the_world.cities_from_team(the_line.block.team) cities_lookup = the_line.the_world.cities_lookup(lower=True) the_team = the_line.the_world.teams()[the_line.block.team] team_dict = the_line.the_world.teams() dead_city = -1 # Handles new_city_name = groups['city_name'] supply_list_s = groups['city_list'] size = int(groups['size'].replace(',', '')) if groups['city_type'] != None: city_type = groups['city_type'].lower().strip() else: city_type = None is_port, is_nomadic = False, False if city_type == "port": is_port = True elif city_type == "nomadic": is_nomadic = True # Get the location from the string try: x, y = groups['location'].split(',') x = int(x.strip()) y = int(y.strip()) terrain = mapper_q.get_terrain(the_line.the_world.cursor, x, y) except Exception as e: return order_block.fail(results, "%s was not founded because trying to read the location produced an error: %s" % (groups['city_name'], e.args[0])) # Rule checks #------------------------ results = order_block.default_line_results(the_line, "%s could not be founded at %s because" % (new_city_name, str((x, y)))) # You can't build on water! if map_data.terrain[terrain] in ('water', 'lake', 'XYZ_1', 'XYZ_2', 'XYZ_3'): return order_block.fail(results, "you cannot build on that terrain (%s)" % map_data.terrain[terrain]) # Does it already exist? if new_city_name.lower() in cities_lookup: existing_city = all_cities[cities_lookup[new_city_name.lower()]] try: if not existing_city.dead: return order_block.fail(results, "%s already exists (controlled by %s)" % (new_city_name, team_dict[existing_city.team].name)) else: # Currently all dead cities need a GM to act return order_block.fail(results, "%s already exists as a dead city, contact Teifion to to fix it (ID:%d)" % (new_city_name, cities_lookup[new_city_name.lower()])) if all_cities[cities_lookup[new_city_name.lower()]].team == the_team.id: dead_city = cities_lookup[new_city_name.lower()] else: return order_block.fail(results, "%s already exists as a dead city, contact Teifion to to fix it (ID:%d)" % (new_city_name, cities_lookup[new_city_name.lower()])) except Exception as e: print(new_city_name.lower()) raise # Is it allowed to be a port? if is_port: if map_data.terrain[terrain] != "shore": is_port = False if mapper_q.get_terrain(the_line.the_world.cursor, x-10, y-10) == 0: is_port = True if mapper_q.get_terrain(the_line.the_world.cursor, x-10, y) == 0: is_port = True if mapper_q.get_terrain(the_line.the_world.cursor, x-10, y+10) == 0: is_port = True if mapper_q.get_terrain(the_line.the_world.cursor, x, y-10) == 0: is_port = True if mapper_q.get_terrain(the_line.the_world.cursor, x, y+10) == 0: is_port = True if mapper_q.get_terrain(the_line.the_world.cursor, x+10, y-10) == 0: is_port = True if mapper_q.get_terrain(the_line.the_world.cursor, x+10, y) == 0: is_port = True if mapper_q.get_terrain(the_line.the_world.cursor, x+10, y+10) == 0: is_port = True if not is_port: return order_block.fail(results, "%s that is not next to the sea" % str((x, y))) # Supply list supply_dict_raw = {} if supply_list_s != None: supply_list_s = supply_list_s.split(",") for s in supply_list_s: sls = s.lower().strip() if sls in cities_lookup: supply_dict_raw[cities_lookup[sls]] = 9999999 else: for c in city_dict.keys(): supply_dict_raw[c] = 9999999 # print("") # print(supply_dict_raw) if debug: results['debug'].append("First pass:"******"City ID %d was not in city_dict" % c) return order_block.fail(results, "One or more of the cities used as a source were not valid (ID: %d)" % c) the_city = city_dict[c] if the_city.dead > 0: continue if new_city_continent != path_f.get_continent(the_line.the_world.cursor, (the_city.x, the_city.y)): if not is_port or not the_city.port: if len(supply_dict_raw) == 1: if debug: results['debug'].append("Continent of target: %s" % new_city_continent) results['debug'].append("Continent of supply: %s" % path_f.get_continent(the_line.the_world.cursor, (the_city.x, the_city.y))) if not the_city.port and not is_port: return order_block.fail(results, "the city you supplied is on another contintent and neither this city nor the new one are a port") elif not the_city.port: return order_block.fail(results, "the city you supplied is on another contintent and the existing city is not a port") elif not is_port: return order_block.fail(results, "the city you supplied is on another contintent and the new city is not a port") else: raise Exception("No handle") if not is_port: if debug: results['debug'].append("Skipped %s due to the new city being on another landmass and not a port" % (city_dict[c].name)) elif not the_city.port: if debug: results['debug'].append("Skipped %s due to it not being a port" % (city_dict[c].name)) # We need both to be a port if they're on different landmasses continue path_data = path_f.path(the_line.the_world.cursor, [(the_city.x, the_city.y), (x, y)], move_speed="Sailing", move_type="Sail") supply_dict[c] = path_data.time_cost if debug: results['debug'].append("Added %s to dict with %s (sailing)" % (city_dict[c].name, int(path_data.time_cost))) else:# Same continent path_data = path_f.path(the_line.the_world.cursor, [(the_city.x, the_city.y), (x, y)], move_speed="Colonists", move_type="Colonists") supply_dict[c] = path_data.time_cost if debug: results['debug'].append("Added %s to dict with %s (walking)" % (city_dict[c].name, int(path_data.time_cost))) # Work out if it's in range if supply_dict[c] > 182:# 6 months if debug: results['debug'].append("Removed %s from dict with, it took %s" % (city_dict[c].name, int(supply_dict[c]))) del(supply_dict[c]) if debug: results['debug'].append("\nCities in range:") for k, v in supply_dict.items(): results['debug'].append("%s has %s to offer (travel time %s)" % (city_dict[k].name, city_dict[k].population, int(v))) # results['debug'].append("") cities_changed = True while cities_changed: if len(supply_dict) < 1: if groups['city_list'] == None: return order_block.fail(results, "the cities able to reach the new location were too far away or not large enough") else: return order_block.fail(results, "the cities able to reach the new location from the list provided were too far away or not large enough") cities_to_delete = [] cities_changed = False city_count = len(supply_dict) amount_per_city = math.ceil(size/city_count) for c in supply_dict.keys(): # Check the city size if city_dict[c].population < amount_per_city: # if debug: print("deleted %s (%s) pop:%s < %s" % (c, city_dict[c].name, supply_dict[c], amount_per_city)) cities_to_delete.append(c) cities_changed = True for c in cities_to_delete: del(supply_dict[c]) # Get cost results['cost'] = res_dict.Res_dict("Materials:%s" % (size/1000)) # Check affordability affordability = the_team.resources.affordable(results['cost'])[0] if not affordability and "Materials" not in the_team.overbudget: return order_block.fail_cost(results) # EXECUTION #------------------------ # Queries results['queries'].append("-- Founding %s at %s for team:%d" % (new_city_name, str((x, y)), the_team.id)) if dead_city > 1: results['queries'].extend(city_f.make_remove_dead_city_query(dead_city)) results['queries'].extend(city_f.make_founding_query( the_team.id, new_city_name, (x, y), is_port, is_nomadic, size, supply_dict.keys()) ) # Apply cost for c in supply_dict.keys(): city_dict[c].population -= (size/len(supply_dict)) the_team.resources -= results['cost'].discrete() # Result results['results'].append("%s was successfully founded at %s at a size of %s" % (new_city_name, str((x, y)), size)) return order_block.success(results)
def migration_order(the_line, groups, debug=False): results = order_block.default_line_results(the_line) city_dict = the_line.the_world.cities_from_team(the_line.block.team) cities_lookup = the_line.the_world.cities_lookup(lower=True) # Can we find the city? if groups['city'].lower() not in cities_lookup: return order_block.fail(results, "there is no city by the name of '%s'" % groups['city']) else: the_city = city_dict[cities_lookup[groups['city'].lower()]] the_team = the_line.the_world.teams()[the_line.block.team] # Target try: target = (int(groups['x']), int(groups['y'])) except Exception as e: return order_block.fail(results, "%s was not moved because trying to read the target location produced an error: %s" % (groups['city_name'], e.args[0])) # Default result results = order_block.default_line_results(the_line, "%s could not be moved to %s because" % (the_city.name, str(target))) # First we check it's on the same continent, if it's not you can't migrate there our_continent = path_f.get_continent(the_line.the_world.cursor, (the_city.x, the_city.y)) target_continent = path_f.get_continent(the_line.the_world.cursor, target) # Before we path, lets check that they can walk there if our_continent != target_continent: if target_continent == None:# or target_continent == -1: return order_block.fail(results, "the target is the ocean") else: return order_block.fail(results, "the target is on a different island") terrain = mapper_q.get_terrain(the_line.the_world.cursor, target[0], target[1]) if map_data.terrain[terrain] in ('lake', 'XYZ_1', 'XYZ_2', 'XYZ_3'): return order_block.fail(results, "you cannot migrate to that terrain (%s)" % map_data.terrain[terrain]) # Pathing time! journey = path_f.path( cursor=the_line.the_world.cursor, waypoints=[(the_city.x, the_city.y), target], move_speed="Nomads", move_type="Nomads") # Does this take more than a year? travel_time = journey.time_cost actual_target = target current_time = 0 if journey.time_cost > 365: for s in journey.steps: if current_time + s['time_cost'] <= 365: actual_target = s['tile'] current_time += s['time_cost'] travel_time = current_time if actual_target != target: results['results'].append("%s migrated to %s, to migrate to %s will take another %s days" % (the_city.name, str(actual_target), str(target), (journey.time_cost - travel_time))) else: results['results'].append("%s migrated to %s" % (the_city.name, str(target))) # Get the query to make it happen results['queries'].extend(city_f.make_migration_query(the_city.id, actual_target, travel_time)) return order_block.success(results)
def build_distance_matrix(the_world, verbose=False): borders = the_world.relations() city_dict = the_world.live_cities() # print(borders) # exit() distance_matrix = {} city_contintent = {} func_start = time.time() i = 0 paths = 0 it = city_dict.items() if verbose: it = cli_f.progressbar(city_dict.items(), "Pathing:", 60, True) worst_land_path = (-1, -1, -1) worst_water_path = (-1, -1, -1) for i1, c1 in it: # i1 = 1224 # c1 = city_dict[i1] started = time.time() links = 0 i += 1 if i1 not in city_contintent: city_contintent[i1] = path_f.get_continent(the_world.cursor, (c1.x, c1.y)) for i2, c2 in city_dict.items(): # i2 = 1280 # c2 = city_dict[i2] # print() # print(city_contintent[i1]) # print(path_f.get_continent(the_world.cursor, (c2.x, c2.y))) # A city can't trade with itself... if i2 == i1: continue # Check we've not already done this if (i1, i2) in distance_matrix: continue # Check borders # [Host][Visitor] # if borders.get[c2.team][c1.team] <= team.border_states.index("Closed"): if the_world.get_border(c2.team, c1.team) <= team.border_states.index("Closed"): continue # if borders[c1.team][c2.team] <= team.border_states.index("At war"): if the_world.get_border(c1.team, c2.team) <= team.border_states.index("At war"): continue # Get continent stuff if i2 not in city_contintent: city_contintent[i2] = path_f.get_continent(the_world.cursor, (c2.x, c2.y)) # Reset distance dist = None crow_dist = path_f.pythagoras_cities(c1, c2) # If on same continent if city_contintent[i1] == city_contintent[i2] and crow_dist < sad_rules.max_crow_dist_land: if crow_dist <= sad_rules.min_trade_distance: dist = DeadPath(sad_rules.min_trade_distance) else: path_time = time.time() try: dist = path_f.path(the_world.cursor, [(c1.x, c1.y), (c2.x, c2.y)], move_speed="Merchant", move_type="Merchant", exception_in_timeout=True, timeout_limit=1000) paths += 1 except Exception as e: print("Trying to path %s (%d) to %s (%d)" % (c1.name, i1, c2.name, i2)) print("Continent %s to %s" % (city_contintent[i1], city_contintent[i2])) raise path_time = time.time() - path_time if path_time > worst_land_path[2]: worst_land_path = ((c1.x, c1.y), (c2.x, c2.y), path_time, "points={0}%2C+{1}%2C+{2}%2C+{3}&move_speed=Merchant&move_type=Merchant".format( c1.x, c1.y, c2.x, c2.y )) # If both are ports then we can try the water if c1.port and c2.port and crow_dist < sad_rules.max_crow_dist_water: path_time = time.time() try: dist_sea = path_f.path(the_world.cursor, [(c1.x, c1.y), (c2.x, c2.y)], move_speed="Sailing", move_type="Sail", exception_in_timeout=True) paths += 1 except Exception as e: print("Trying to path %s (%d) to %s (%d)" % (c1.name, i1, c2.name, i2)) raise path_time = time.time() - path_time if path_time > worst_water_path[2]: worst_water_path = ((c1.x, c1.y), (c2.x, c2.y), path_time, "points={0}%2C+{1}%2C+{2}%2C+{3}&move_speed=Sailing&move_type=Sail".format( c1.x, c1.y, c2.x, c2.y )) # Now pick the fastest if dist == None or dist.time_cost > dist_sea.time_cost: dist = dist_sea # Is it none? if dist == None: time_cost = 99999 else: time_cost = sad_rules.trade_distance(dist.time_cost) links += 1 # if borders[c2.team][c1.team] == team.border_states.index("Segregated"): if the_world.get_border(c2.team, c1.team) <= team.border_states.index("Segregated"): time_cost *= sad_rules.segregated_multiplier if time_cost > sad_rules.max_trade_travel_time: continue distance_matrix[(i1, i2)] = time_cost # If we have the same borders round the other way then we can skip this path later # if borders[c2.team][c1.team] == borders[c1.team][c2.team]: if the_world.get_border(c2.team, c1.team) == the_world.get_border(c1.team, c2.team): distance_matrix[(i2, i1)] = distance_matrix[(i1, i2)] # print("%d of %d in %ss (%d links)" % (i, len(city_dict), round(time.time() - started, 2), links)) """ Origional Tried 103,505 paths Completed in 446 seconds After applying: "if (i1, i2) in distance_matrix" with same border checking Tried 79,979 paths Completed in 338 seconds After dropping the timeout exception from 10k to 1k Tried 79,979 paths Completed in 335 seconds After adding in the min distance (10) Tried 79,958 paths Completed in 337 seconds Adding in dead tiles and a smaller (10 not 1000) move cost Tried 79,958 paths Completed in 336 seconds """ # Some stats stuff if verbose: print("Worst land path: %s -> %s in %s = http://localhost/rob3/web.py?mode=path_map&%s" % worst_land_path) print("Worst water path: %s -> %s in %s = http://localhost/rob3/web.py?mode=path_map&%s" % worst_water_path) total_time = time.time() - func_start print("Tried %s paths in %s seconds, avg %ss per path or %s paths per second" % (format(paths, ','), int(total_time), round(total_time/paths, 3), round(paths/total_time, 2))) # Now to save it q = "INSERT INTO trade_distances (city_1, city_2, distance) values %s;" % ",".join( ["(%d, %d, %d)" % (c[0], c[1], d) for c, d in distance_matrix.items()] ) the_world.cursor.execute("DELETE FROM trade_distances") if verbose: print("Completed in %d seconds" % int(time.time() - func_start)) the_world.cursor.execute(q)