Esempio n. 1
0
def distribute_specials(specials_freq, universe_objects):
    """
    Adds start-of-game specials to universe objects.
    """
    # get basic chance for occurrence of specials from the universe tables
    base_chance = universe_tables.SPECIALS_FREQUENCY[specials_freq]
    if base_chance <= 0:
        return

    # get a list with all specials that have a spawn rate and limit both > 0 and a location condition defined
    # (no location condition means a special shouldn't get added at game start)
    specials = [
        sp for sp in fo.get_all_specials() if fo.special_spawn_rate(sp) > 0.0
        and fo.special_spawn_limit(sp) > 0 and fo.special_has_location(sp)
    ]
    if not specials:
        return

    # dump a list of all specials meeting that conditions and their properties to the log
    print("Specials available for distribution at game start:")
    for special in specials:
        print("... {:30}: spawn rate {:2.3f} / spawn limit {}".format(
            special, fo.special_spawn_rate(special),
            fo.special_spawn_limit(special)))

    objects_needing_specials = [
        obj for obj in universe_objects if random.random() < base_chance
    ]

    track_num_placed = {obj: 0 for obj in universe_objects}

    print(
        "Base chance for specials is {}. Placing specials on {} of {} ({:1.4f})objects"
        .format(
            base_chance,
            len(objects_needing_specials),
            len(universe_objects),
            float(len(objects_needing_specials)) / len(universe_objects),
        ))

    obj_tuple_needing_specials = set(
        zip(
            objects_needing_specials,
            fo.objs_get_systems(objects_needing_specials),
            calculate_number_of_specials_to_place(objects_needing_specials),
        ))

    # Equal to the largest distance in WithinStarlaneJumps conditions
    # GALAXY_DECOUPLING_DISTANCE is used as follows.  For any two or more objects
    # at least GALAXY_DECOUPLING_DISTANCE appart you only need to check
    # fo.special_locations once and then you can place as many specials as possible,
    # subject to number restrictions.
    #
    # Organize the objects into sets where all objects are spaced GALAXY_DECOUPLING_DISTANCE
    # appart.  Place a special on each one.  Repeat until you run out of specials or objects.
    GALAXY_DECOUPLING_DISTANCE = 6

    while obj_tuple_needing_specials:
        systems_needing_specials = defaultdict(set)
        for (obj, system, specials_count) in obj_tuple_needing_specials:
            systems_needing_specials[system].add((obj, system, specials_count))

        print(" Placing in {} locations remaining.".format(
            len(systems_needing_specials)))

        # Find a list of candidates all spaced GALAXY_DECOUPLING_DISTANCE apart
        candidates = []
        while systems_needing_specials:
            random_sys = random.choice(list(systems_needing_specials.values()))
            member = random.choice(list(random_sys))
            obj, system, specials_count = member
            candidates.append(obj)
            obj_tuple_needing_specials.remove(member)
            if specials_count > 1:
                obj_tuple_needing_specials.add(
                    (obj, system, specials_count - 1))

            # remove all neighbors from the local pool
            for neighbor in fo.systems_within_jumps_unordered(
                    GALAXY_DECOUPLING_DISTANCE, [system]):
                if neighbor in systems_needing_specials:
                    systems_needing_specials.pop(neighbor)

        print("Caching specials_locations() at {} of {} remaining locations.".
              format(str(len(candidates)),
                     str(len(obj_tuple_needing_specials) + len(candidates))))
        # Get the locations at which each special can be placed
        locations_cache = {}
        for special in specials:
            # The fo.special_locations in the following line consumes most of the time in this
            # function.  Decreasing GALAXY_DECOUPLING_DISTANCE will speed up the whole
            # function by reducing the number of times this needs to be called.
            locations_cache[special] = set(
                fo.special_locations(special, candidates))

        # Attempt to apply a special to each candidate
        # by finding a special that can be applied to it and hasn't been added too many times
        for obj in candidates:

            # check if the spawn limit for this special has already been reached (that is, if this special
            # has already been added the maximal allowed number of times)
            specials = [
                s for s in specials if universe_statistics.specials_summary[s]
                < fo.special_spawn_limit(s)
            ]
            if not specials:
                break

            # Find which specials can be placed at this one location
            local_specials = [
                sp for sp in specials if obj in locations_cache[sp]
            ]
            if not local_specials:
                universe_statistics.specials_repeat_dist[0] += 1
                continue

            # All prerequisites and the test have been met, now add this special to this universe object.
            track_num_placed[obj] += place_special(local_specials, obj)

    for num_placed in track_num_placed.values():
        universe_statistics.specials_repeat_dist[num_placed] += 1
Esempio n. 2
0
def generate_monsters(monster_freq, systems):
    """
    Adds space monsters to systems.
    """
    # first, calculate the basic chance for monster generation in a system
    # based on the monster frequency that has been passed
    # get the corresponding value for the specified monster frequency from the universe tables
    basic_chance = universe_tables.MONSTER_FREQUENCY[monster_freq]
    # a value of 0 means no monsters, in this case return immediately
    if basic_chance <= 0:
        return
    print "Default monster spawn chance:", basic_chance
    expectation_tally = 0.0
    actual_tally = 0

    # get all monster fleets that have a spawn rate and limit both > 0 and at least one monster ship design in it
    # (a monster fleet with no monsters in it is pointless) and store them in a list
    fleet_plans = fo.load_monster_fleet_plan_list()

    # create a map where we store a spawn counter for each monster fleet
    # this counter will be set to the spawn limit initially and decreased every time the monster fleet is spawned
    # this map (dict) needs to be separate from the list holding the fleet plans because the order in which items
    # are stored in a dict is undefined (can be different each time), which would result in different distribution
    # even when using the same seed for the RNG
    spawn_limits = {fp: fp.spawn_limit() for fp in fleet_plans
                    if fp.spawn_rate() > 0.0 and fp.spawn_limit() > 0 and fp.ship_designs()}

    # map nests to monsters for ease of reporting
    nest_name_map = {"KRAKEN_NEST_SPECIAL": "SM_KRAKEN_1",
                     "SNOWFLAKE_NEST_SPECIAL": "SM_SNOWFLAKE_1",
                     "JUGGERNAUT_NEST_SPECIAL": "SM_JUGGERNAUT_1"}
    tracked_plan_tries = {name: 0 for name in nest_name_map.values()}
    tracked_plan_counts = {name: 0 for name in nest_name_map.values()}
    tracked_plan_valid_locations = {fp: 0 for fp in fleet_plans if fp.name() in tracked_plan_counts}

    if not fleet_plans:
        return

    universe = fo.get_universe()

    # Fleet plans that include ships capable of altering starlanes.
    # @content_tag{CAN_ALTER_STARLANES} universe_generator special handling
    # for fleets containing a hull design with this tag.
    fleet_can_alter_starlanes = {fp for fp in fleet_plans
                                 if any([universe.getGenericShipDesign(design).hull_type.hasTag("CAN_ALTER_STARLANES")
                                         for design in fp.ship_designs()])}

    # dump a list of all monster fleets meeting these conditions and their properties to the log
    print "Monster fleets available for generation at game start:"
    fp_location_cache = {}
    for fleet_plan in fleet_plans:
        print "...", fleet_plan.name(), ": spawn rate", fleet_plan.spawn_rate(),
        print "/ spawn limit", fleet_plan.spawn_limit(),
        print "/ effective chance", basic_chance * fleet_plan.spawn_rate(),
        fp_location_cache[fleet_plan] = set(fleet_plan.locations(systems))
        print ("/ can be spawned at", len(fp_location_cache[fleet_plan]),
               "of", len(systems), "systems")
        if fleet_plan.name() in nest_name_map.values():
            universe_statistics.tracked_monsters_chance[fleet_plan.name()] = basic_chance * fleet_plan.spawn_rate()

    # initialize a manager for monsters that can alter the map
    # required to prevent their placement from disjoining the map
    starlane_altering_monsters = StarlaneAlteringMonsters(systems)

    # collect info for tracked monster nest valid locations
    planets = [p for s in systems for p in fo.sys_get_planets(s)]
    tracked_nest_valid_locations = {nest: len(fo.special_locations(nest, planets))
                                    for nest in nest_name_map}

    # for each system in the list that has been passed to this function, find a monster fleet that can be spawned at
    # the system and which hasn't already been added too many times, then attempt to add that monster fleet by
    # testing the spawn rate chance
    random.shuffle(systems)
    for system in systems:
        # collect info for tracked monster valid locations
        for fp in tracked_plan_valid_locations:
            if system in fp_location_cache[fp]:
                tracked_plan_valid_locations[fp] += 1

        # filter out all monster fleets whose location condition allows this system and whose counter hasn't reached 0.
        suitable_fleet_plans = [fp for fp in fleet_plans
                                if system in fp_location_cache[fp]
                                and spawn_limits.get(fp, 0)
                                and (fp not in fleet_can_alter_starlanes
                                     or starlane_altering_monsters.can_place_at(system, fp))]
        # if there are no suitable monster fleets for this system, continue with the next
        if not suitable_fleet_plans:
            continue

        # randomly select one monster fleet out of the suitable ones and then test if we want to add it to this system
        # by making a roll against the basic chance multiplied by the spawn rate of this monster fleet
        expectation_tally += basic_chance * sum([fp.spawn_rate()
                                                 for fp in suitable_fleet_plans]) / len(suitable_fleet_plans)
        fleet_plan = random.choice(suitable_fleet_plans)
        if fleet_plan.name() in tracked_plan_tries:
            tracked_plan_tries[fleet_plan.name()] += 1
        if random.random() > basic_chance * fleet_plan.spawn_rate():
            print("\t\t At system %4d rejected monster fleet %s from %d suitable fleets"
                  % (system, fleet_plan.name(), len(suitable_fleet_plans)))
            # no, test failed, continue with the next system
            continue
        actual_tally += 1
        if fleet_plan.name() in tracked_plan_counts:
            tracked_plan_counts[fleet_plan.name()] += 1

        # all prerequisites and the test have been met, now spawn this monster fleet in this system
        # create monster fleet
        try:
            if fleet_plan in fleet_can_alter_starlanes:
                starlane_altering_monsters.place(system, fleet_plan)
            else:
                populate_monster_fleet(fleet_plan, system)
            # decrement counter for this monster fleet
            spawn_limits[fleet_plan] -= 1

        except MapGenerationError as err:
            report_error(str(err))
            continue

    print "Actual # monster fleets placed: %d; Total Placement Expectation: %.1f" % (actual_tally, expectation_tally)
    # finally, compile some statistics to be dumped to the log later
    universe_statistics.monsters_summary = [
        (fp.name(), fp.spawn_limit() - counter) for fp, counter in spawn_limits.iteritems()
    ]
    universe_statistics.tracked_monsters_tries.update(tracked_plan_tries)
    universe_statistics.tracked_monsters_summary.update(tracked_plan_counts)
    universe_statistics.tracked_monsters_location_summary.update(
        (fp.name(), count) for fp, count in tracked_plan_valid_locations.iteritems())
    universe_statistics.tracked_nest_location_summary.update(
        (nest_name_map[nest], count) for nest, count in tracked_nest_valid_locations.items())
Esempio n. 3
0
def distribute_specials(specials_freq, universe_objects):
    """
    Adds start-of-game specials to universe objects.
    """
    # get basic chance for occurrence of specials from the universe tables
    base_chance = universe_tables.SPECIALS_FREQUENCY[specials_freq]
    if base_chance <= 0:
        return

    # get a list with all specials that have a spawn rate and limit both > 0 and a location condition defined
    # (no location condition means a special shouldn't get added at game start)
    specials = [sp for sp in fo.get_all_specials() if fo.special_spawn_rate(sp) > 0.0 and
                fo.special_spawn_limit(sp) > 0 and fo.special_has_location(sp)]
    if not specials:
        return

    # dump a list of all specials meeting that conditions and their properties to the log
    print "Specials available for distribution at game start:"
    for special in specials:
        print("... {:30}: spawn rate {:2.3f} / spawn limit {}".
              format(special, fo.special_spawn_rate(special), fo.special_spawn_limit(special)))

    objects_needing_specials = [obj for obj in universe_objects if random.random() < base_chance]

    track_num_placed = {obj: 0 for obj in universe_objects}

    print("Base chance for specials is {}. Placing specials on {} of {} ({:1.4f})objects"
          .format(base_chance, len(objects_needing_specials), len(universe_objects),
                  float(len(objects_needing_specials)) / len(universe_objects)))

    obj_tuple_needing_specials = set(zip(objects_needing_specials,
                                         fo.objs_get_systems(objects_needing_specials),
                                         calculate_number_of_specials_to_place(objects_needing_specials)))

    # Equal to the largest distance in WithinStarlaneJumps conditions
    # GALAXY_DECOUPLING_DISTANCE is used as follows.  For any two or more objects
    # at least GALAXY_DECOUPLING_DISTANCE appart you only need to check
    # fo.special_locations once and then you can place as many specials as possible,
    # subject to number restrictions.
    #
    # Organize the objects into sets where all objects are spaced GALAXY_DECOUPLING_DISTANCE
    # appart.  Place a special on each one.  Repeat until you run out of specials or objects.
    GALAXY_DECOUPLING_DISTANCE = 6

    while obj_tuple_needing_specials:
        systems_needing_specials = defaultdict(set)
        for (obj, system, specials_count) in obj_tuple_needing_specials:
            systems_needing_specials[system].add((obj, system, specials_count))

        print " Placing in {} locations remaining.".format(len(systems_needing_specials))

        # Find a list of candidates all spaced GALAXY_DECOUPLING_DISTANCE apart
        candidates = []
        while systems_needing_specials:
            random_sys = random.choice(systems_needing_specials.values())
            member = random.choice(list(random_sys))
            obj, system, specials_count = member
            candidates.append(obj)
            obj_tuple_needing_specials.remove(member)
            if specials_count > 1:
                obj_tuple_needing_specials.add((obj, system, specials_count - 1))

            # remove all neighbors from the local pool
            for neighbor in fo.systems_within_jumps_unordered(GALAXY_DECOUPLING_DISTANCE, [system]):
                if neighbor in systems_needing_specials:
                    systems_needing_specials.pop(neighbor)

        print("Caching specials_locations() at {} of {} remaining locations.".
              format(str(len(candidates)), str(len(obj_tuple_needing_specials) + len(candidates))))
        # Get the locations at which each special can be placed
        locations_cache = {}
        for special in specials:
            # The fo.special_locations in the following line consumes most of the time in this
            # function.  Decreasing GALAXY_DECOUPLING_DISTANCE will speed up the whole
            # function by reducing the number of times this needs to be called.
            locations_cache[special] = set(fo.special_locations(special, candidates))

        # Attempt to apply a special to each candidate
        # by finding a special that can be applied to it and hasn't been added too many times
        for obj in candidates:

            # check if the spawn limit for this special has already been reached (that is, if this special
            # has already been added the maximal allowed number of times)
            specials = [s for s in specials if universe_statistics.specials_summary[s] < fo.special_spawn_limit(s)]
            if not specials:
                break

            # Find which specials can be placed at this one location
            local_specials = [sp for sp in specials if obj in locations_cache[sp]]
            if not local_specials:
                universe_statistics.specials_repeat_dist[0] += 1
                continue

            # All prerequisites and the test have been met, now add this special to this universe object.
            track_num_placed[obj] += place_special(local_specials, obj)

    for num_placed in track_num_placed.values():
        universe_statistics.specials_repeat_dist[num_placed] += 1
Esempio n. 4
0
def generate_monsters(monster_freq, systems):
    """
    Adds space monsters to systems.
    """
    # first, calculate the basic chance for monster generation in a system
    # based on the monster frequency that has been passed
    # get the corresponding value for the specified monster frequency from the universe tables
    basic_chance = universe_tables.MONSTER_FREQUENCY[monster_freq]
    # a value of 0 means no monsters, in this case return immediately
    if basic_chance <= 0:
        return
    print "Default monster spawn chance:", basic_chance
    expectation_tally = 0.0
    actual_tally = 0

    # get all monster fleets that have a spawn rate and limit both > 0 and at least one monster ship design in it
    # (a monster fleet with no monsters in it is pointless) and store them in a list
    fleet_plans = fo.load_monster_fleet_plan_list()

    # create a map where we store a spawn counter for each monster fleet
    # this counter will be set to the spawn limit initially and decreased every time the monster fleet is spawned
    # this map (dict) needs to be separate from the list holding the fleet plans because the order in which items
    # are stored in a dict is undefined (can be different each time), which would result in different distribution
    # even when using the same seed for the RNG
    spawn_limits = {fp: fp.spawn_limit() for fp in fleet_plans
                    if fp.spawn_rate() > 0.0 and fp.spawn_limit() > 0 and fp.ship_designs()}

    # map nests to monsters for ease of reporting
    nest_name_map = {"KRAKEN_NEST_SPECIAL": "SM_KRAKEN_1",
                     "SNOWFLAKE_NEST_SPECIAL": "SM_SNOWFLAKE_1",
                     "JUGGERNAUT_NEST_SPECIAL": "SM_JUGGERNAUT_1"}
    tracked_plan_tries = {name: 0 for name in nest_name_map.values()}
    tracked_plan_counts = {name: 0 for name in nest_name_map.values()}
    tracked_plan_valid_locations = {fp: 0 for fp in fleet_plans if fp.name() in tracked_plan_counts}

    if not fleet_plans:
        return

    universe = fo.get_universe()

    # Fleet plans that include ships capable of altering starlanes.
    # @content_tag{CAN_ALTER_STARLANES} universe_generator special handling for fleets containing a hull design with this tag.
    fleet_can_alter_starlanes = {fp for fp in fleet_plans
                                 if any([universe.getGenericShipDesign(design).hull_type.hasTag("CAN_ALTER_STARLANES")
                                         for design in fp.ship_designs()])}

    # dump a list of all monster fleets meeting these conditions and their properties to the log
    print "Monster fleets available for generation at game start:"
    fp_location_cache = {}
    for fleet_plan in fleet_plans:
        print "...", fleet_plan.name(), ": spawn rate", fleet_plan.spawn_rate(),
        print "/ spawn limit", fleet_plan.spawn_limit(),
        print "/ effective chance", basic_chance * fleet_plan.spawn_rate(),
        fp_location_cache[fleet_plan] = set(fleet_plan.locations(systems))
        print ("/ can be spawned at", len(fp_location_cache[fleet_plan]),
               "of", len(systems), "systems")
        if fleet_plan.name() in nest_name_map.values():
            universe_statistics.tracked_monsters_chance[fleet_plan.name()] = basic_chance * fleet_plan.spawn_rate()

    # initialize a manager for monsters that can alter the map
    # required to prevent their placement from disjoining the map
    starlane_altering_monsters = StarlaneAlteringMonsters(systems)

    # collect info for tracked monster nest valid locations
    planets = [p for s in systems for p in fo.sys_get_planets(s)]
    tracked_nest_valid_locations = {nest: len(fo.special_locations(nest, planets))
                                    for nest in nest_name_map}

    # for each system in the list that has been passed to this function, find a monster fleet that can be spawned at
    # the system and which hasn't already been added too many times, then attempt to add that monster fleet by
    # testing the spawn rate chance
    random.shuffle(systems)
    for system in systems:
        # collect info for tracked monster valid locations
        for fp in tracked_plan_valid_locations:
            if system in fp_location_cache[fp]:
                tracked_plan_valid_locations[fp] += 1

        # filter out all monster fleets whose location condition allows this system and whose counter hasn't reached 0.
        suitable_fleet_plans = [fp for fp in fleet_plans
                                if system in fp_location_cache[fp]
                                and spawn_limits[fp]
                                and (fp not in fleet_can_alter_starlanes
                                     or starlane_altering_monsters.can_place_at(system, fp))]
        # if there are no suitable monster fleets for this system, continue with the next
        if not suitable_fleet_plans:
            continue

        # randomly select one monster fleet out of the suitable ones and then test if we want to add it to this system
        # by making a roll against the basic chance multiplied by the spawn rate of this monster fleet
        expectation_tally += basic_chance * sum([fp.spawn_rate()
                                                 for fp in suitable_fleet_plans]) / len(suitable_fleet_plans)
        fleet_plan = random.choice(suitable_fleet_plans)
        if fleet_plan.name() in tracked_plan_tries:
            tracked_plan_tries[fleet_plan.name()] += 1
        if random.random() > basic_chance * fleet_plan.spawn_rate():
            print("\t\t At system %4d rejected monster fleet %s from %d suitable fleets"
                  % (system, fleet_plan.name(), len(suitable_fleet_plans)))
            # no, test failed, continue with the next system
            continue
        actual_tally += 1
        if fleet_plan.name() in tracked_plan_counts:
            tracked_plan_counts[fleet_plan.name()] += 1

        # all prerequisites and the test have been met, now spawn this monster fleet in this system
        # create monster fleet
        try:
            if fleet_plan in fleet_can_alter_starlanes:
                starlane_altering_monsters.place(system, fleet_plan)
            else:
                populate_monster_fleet(fleet_plan, system)
            # decrement counter for this monster fleet
            spawn_limits[fleet_plan] -= 1

        except MapGenerationError as err:
            report_error(str(err))
            continue

    print "Actual # monster fleets placed: %d; Total Placement Expectation: %.1f" % (actual_tally, expectation_tally)
    # finally, compile some statistics to be dumped to the log later
    universe_statistics.monsters_summary = [(fp.name(), fp.spawn_limit() - counter) for fp, counter in spawn_limits.iteritems()]
    universe_statistics.tracked_monsters_tries.update(tracked_plan_tries)
    universe_statistics.tracked_monsters_summary.update(tracked_plan_counts)
    universe_statistics.tracked_monsters_location_summary.update([(fp.name(), count)
                                                         for fp, count in tracked_plan_valid_locations.iteritems()])
    universe_statistics.tracked_nest_location_summary.update([(nest_name_map[nest], count)
                                                     for nest, count in tracked_nest_valid_locations.items()])