Skip to content

Cython Extensions SC2

cy_adjust_moving_formation(our_units, target, fodder_tags, unit_multiplier, retreat_angle)

Adjust units formation.

Big thanks to idontcodethisgame for the original code in Eris

The idea here is that we give UnitTypeId's a fodder value, and this cython function works out which unit we want at the front to absorb damage. This works by returning a dictionary containing tags of the non fodder units that need to move backwards behind the fodder and the position they should move to.

TIP: Don't use this when combat is already active, will probably lead to anti-micro. Use this while moving across the map and pre combat.

Example:

import numpy as np
from cython_extensions import cy_find_aoe_position, cy_find_units_center_mass
from sc2.ids.ability_id import AbilityId
from sc2.ids.unit_typeid import UnitTypeId
from sc2.position import Point2
from sc2.units import Units
from sc2.unit import Unit

def detect_fodder_value(self, units) -> int:

    # zealot will always be fodder
    # if no zealot this will pick the next best unit type
    # stalker will never be fodder in this example
    unit_fodder_values: dict[UnitTypeId, int] = {
            UnitTypeId.STALKER: 4,
            UnitTypeId.ZEALOT: 1,
            UnitTypeId.ADEPT: 2,
            UnitTypeId.PROBE: 3,
    }

    # establish how many fodder levels there are
    unit_type_fodder_values: set[int] = {
        unit_fodder_values[u.type_id]
        for u in units
        if u.type_id in unit_fodder_values
    }

    # if there's only one fodder level, no units are fodder
    if len(unit_type_fodder_values) > 1:
        return min(unit_type_fodder_values)
    else:
        return 0

async def on_step(self, iteration: int):
    if not self.enemy_units:
        return
    # find lowest fodder value among our units
    # will return 0 if our army only has one fodder level
    fodder_value: int = self.detect_fodder_value(self.units)

    fodder_tags = []
    units_that_need_to_move = dict()

    # there are fodder levels, calculate unit adjustment
    if fodder_value > 0:
        for unit in self.units:
            if (
                unit.type_id in self.unit_fodder_values
                and self.unit_fodder_values[unit.type_id] == fodder_value
            ):
                fodder_tags.append(unit.tag)

        units_that_need_to_move = cy_adjust_moving_formation(
            self.units,
            cy_find_units_center_mass(self.enemy_units, 5.0)[0],
            fodder_tags,
            1.0,
            0.25,
        )

    for unit in self.units:
        if (
            unit.tag in units_that_need_to_move
            and unit.distance_to(self.enemy_units.center) > 9.0
        ):
            # in practise check this position is valid (pathable, in bounds etc)
            # left out here for example clarity
            unit.move(Point2(units_that_need_to_move[unit.tag]))
        else:
            unit.attack(self.enemy_units.center)

324 µs ± 9.44 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

Parameters:

Name Type Description Default
our_units Union[Units, list[Unit]]

All our squad units, including core and fodder.

required
target Union[Point2, tuple[float, float]]

The target we want the fodder to lead us to.

required
fodder_tags list[int]

A list of fodder unit tags.

required
unit_multiplier float

How far core units should retreat when adjusting during combat.

required
retreat_angle float

Angle (in radians) for diagonal retreat of core units.

required

Returns:

Type Description
dict[int, tuple[float, float]]

A dictionary where keys are unit tags requiring movement

dict[int, tuple[float, float]]

and values are tuples of x, y coordinates.

Source code in cython_extensions/combat_utils.pyi
def cy_adjust_moving_formation(
        our_units: Union[Units, list[Unit]],
        target: Union[Point2, tuple[float, float]],
        fodder_tags: list[int],
        unit_multiplier: float,
        retreat_angle: float
) -> dict[int, tuple[float, float]]:
    """Adjust units formation.

    Big thanks to idontcodethisgame for the original code in Eris

    The idea here is that we give UnitTypeId's a fodder value,
    and this cython function works out which unit we want at
    the front to absorb damage. This works by returning a dictionary
    containing tags of the non fodder units that need to move backwards
    behind the fodder and the position they should move to.

    TIP: Don't use this when combat is already active, will
    probably lead to anti-micro. Use this while moving across the
    map and pre combat.

    Example:
    ```py
    import numpy as np
    from cython_extensions import cy_find_aoe_position, cy_find_units_center_mass
    from sc2.ids.ability_id import AbilityId
    from sc2.ids.unit_typeid import UnitTypeId
    from sc2.position import Point2
    from sc2.units import Units
    from sc2.unit import Unit

    def detect_fodder_value(self, units) -> int:

        # zealot will always be fodder
        # if no zealot this will pick the next best unit type
        # stalker will never be fodder in this example
        unit_fodder_values: dict[UnitTypeId, int] = {
                UnitTypeId.STALKER: 4,
                UnitTypeId.ZEALOT: 1,
                UnitTypeId.ADEPT: 2,
                UnitTypeId.PROBE: 3,
        }

        # establish how many fodder levels there are
        unit_type_fodder_values: set[int] = {
            unit_fodder_values[u.type_id]
            for u in units
            if u.type_id in unit_fodder_values
        }

        # if there's only one fodder level, no units are fodder
        if len(unit_type_fodder_values) > 1:
            return min(unit_type_fodder_values)
        else:
            return 0

    async def on_step(self, iteration: int):
        if not self.enemy_units:
            return
        # find lowest fodder value among our units
        # will return 0 if our army only has one fodder level
        fodder_value: int = self.detect_fodder_value(self.units)

        fodder_tags = []
        units_that_need_to_move = dict()

        # there are fodder levels, calculate unit adjustment
        if fodder_value > 0:
            for unit in self.units:
                if (
                    unit.type_id in self.unit_fodder_values
                    and self.unit_fodder_values[unit.type_id] == fodder_value
                ):
                    fodder_tags.append(unit.tag)

            units_that_need_to_move = cy_adjust_moving_formation(
                self.units,
                cy_find_units_center_mass(self.enemy_units, 5.0)[0],
                fodder_tags,
                1.0,
                0.25,
            )

        for unit in self.units:
            if (
                unit.tag in units_that_need_to_move
                and unit.distance_to(self.enemy_units.center) > 9.0
            ):
                # in practise check this position is valid (pathable, in bounds etc)
                # left out here for example clarity
                unit.move(Point2(units_that_need_to_move[unit.tag]))
            else:
                unit.attack(self.enemy_units.center)
    ```

    324 µs ± 9.44 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

    Args:
        our_units: All our squad units, including core and fodder.
        target: The target we want the fodder to lead us to.
        fodder_tags: A list of fodder unit tags.
        unit_multiplier: How far core units should retreat when
            adjusting during combat.
        retreat_angle: Angle (in radians) for diagonal
            retreat of core units.

    Returns:
        A dictionary where keys are unit tags requiring movement
        and values are tuples of x, y coordinates.

    """
    ...

cy_attack_ready(ai, unit, target)

Check if the unit is ready to attack the target.

Takes into account turn rate and unit speeds

Example:

from cython_extensions import cy_attack_ready

worker = self.workers[0]
target = self.enemy_units[0]

attack_ready: bool = cy_attack_ready(self, worker, target)

1.46 µs ± 5.45 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Python alternative:
5.66 µs ± 21.2 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Parameters:

Name Type Description Default
ai BotAI

Bot object that will be running the game.

required
unit Unit

The unit we want to check.

required
target Unit

The thing we want to shoot.

required

Returns:

Type Description
bool

True if the unit is ready to attack the target, False otherwise.

Source code in cython_extensions/combat_utils.pyi
def cy_attack_ready(ai: BotAI, unit: Unit, target: Unit) -> bool:
    """Check if the unit is ready to attack the target.

    Takes into account turn rate and unit speeds

    Example:
    ```py
    from cython_extensions import cy_attack_ready

    worker = self.workers[0]
    target = self.enemy_units[0]

    attack_ready: bool = cy_attack_ready(self, worker, target)
    ```

    ```
    1.46 µs ± 5.45 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

    Python alternative:
    5.66 µs ± 21.2 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
    ```

    Args:
        ai: Bot object that will be running the game.
        unit: The unit we want to check.
        target: The thing we want to shoot.

    Returns:
        True if the unit is ready to attack the target, False otherwise.
        """
    ...

cy_find_aoe_position(effect_radius, targets, bonus_tags=None)

Find best splash target given a group of enemies.

Big thanks to idontcodethisgame for the original code in Eris

WARNING: Please don't spam this function, it's fine to use as required but is costly. For example: only use this if unit ability is ready, only if enemy are in combat range etc.

Example:

import numpy as np
from cython_extensions import cy_find_aoe_position
from sc2.ids.ability_id import AbilityId
from sc2.ids.unit_typeid import UnitTypeId
from sc2.position import Point2
from sc2.units import Units
from sc2.unit import Unit

enemies: Units = self.enemy_units

for unit in self.units:
    if unit.type_id == UnitTypeId.RAVAGER:
        # in practice, don't do this query for every individual unit
        abilities = await self.get_available_abilities(unit)
        if AbilityId.EFFECT_CORROSIVEBILE in abilities:
            target: Optional[np.ndarray] = cy_find_aoe_position(effect_radius=1.375, targets=enemies)
            # in practise in some scenarios, you should do an extra check to
            # count how many units you would hit, this only finds position, not amount
            if pos is not None:
                unit(AbilityId.EFFECT_CORROSIVEBILE, Point2(pos))

Parameters:

Name Type Description Default
effect_radius float

The radius of the effect (range).

required
targets Union[Units, list[Unit]]

All enemy units we would like to check.

required
bonus_tags set[int]

If provided, give more value to these enemy tags.

None

Returns:

Type Description
Optional[ndarray]

A 1D numpy array containing x and y coordinates of aoe position,

Optional[ndarray]

or None.

Source code in cython_extensions/combat_utils.pyi
def cy_find_aoe_position(
    effect_radius: float, targets: Union[Units, list[Unit]], bonus_tags: set[int] = None
) -> Optional[np.ndarray]:
    """Find best splash target given a group of enemies.

    Big thanks to idontcodethisgame for the original code in Eris

    WARNING: Please don't spam this function, it's fine to use as required
    but is costly. For example: only use this if unit ability is ready,
    only if enemy are in combat range etc.

    Example:
    ```py
    import numpy as np
    from cython_extensions import cy_find_aoe_position
    from sc2.ids.ability_id import AbilityId
    from sc2.ids.unit_typeid import UnitTypeId
    from sc2.position import Point2
    from sc2.units import Units
    from sc2.unit import Unit

    enemies: Units = self.enemy_units

    for unit in self.units:
        if unit.type_id == UnitTypeId.RAVAGER:
            # in practice, don't do this query for every individual unit
            abilities = await self.get_available_abilities(unit)
            if AbilityId.EFFECT_CORROSIVEBILE in abilities:
                target: Optional[np.ndarray] = cy_find_aoe_position(effect_radius=1.375, targets=enemies)
                # in practise in some scenarios, you should do an extra check to
                # count how many units you would hit, this only finds position, not amount
                if pos is not None:
                    unit(AbilityId.EFFECT_CORROSIVEBILE, Point2(pos))
    ```

    Args:
        effect_radius: The radius of the effect (range).
        targets: All enemy units we would like to check.
        bonus_tags: If provided, give more value to these enemy tags.

    Returns:
        A 1D numpy array containing x and y coordinates of aoe position,
        or None.
    """
    ...

cy_is_facing(unit, other_unit, angle_error)

Get turn speed of unit in radians

Example:

from cython_extensions import cy_is_facing

unit: Unit = self.workers[0]
other_unit: Unit = self.townhalls[0]
is_facing: bool = cy_is_facing(unit, other_unit)
323 ns ± 3.93 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Python-sc2's `unit.is_facing(other_unit)` alternative:
2.94 µs ± 8 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Parameters:

Name Type Description Default
unit Unit

The actual unit we are checking.

required
other_unit int

The unit type ID integer value.

required
angle_error float

Some leeway when deciding if a unit is facing the other unit.

required

Returns:

Type Description
bool

True if the unit is facing the other unit, False otherwise.

Source code in cython_extensions/combat_utils.pyi
def cy_is_facing(unit: Unit, other_unit: int, angle_error: float) -> bool:
    """Get turn speed of unit in radians

    Example:
    ```py
    from cython_extensions import cy_is_facing

    unit: Unit = self.workers[0]
    other_unit: Unit = self.townhalls[0]
    is_facing: bool = cy_is_facing(unit, other_unit)
    ```
    ```
    323 ns ± 3.93 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

    Python-sc2's `unit.is_facing(other_unit)` alternative:
    2.94 µs ± 8 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
    ```

    Args:
        unit: The actual unit we are checking.
        other_unit: The unit type ID integer value.
        angle_error: Some leeway when deciding if a unit is facing the other unit.
        Defaults to 0.3.

    Returns:
        True if the unit is facing the other unit, False otherwise.

    """
    ...

cy_pick_enemy_target(enemies)

Pick the best thing to shoot at out of all enemies.

Example:

from cython_extensions import cy_pick_enemy_target
from sc2.units import Units
from sc2.unit import Unit

enemies: Units = self.enemy_units

target: Unit = cy_pick_enemy_target(enemies)
70.5 µs ± 818 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Python alternative:
115 µs ± 766 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Parameters:

Name Type Description Default
enemies Union[Units, list[Unit]]

All enemy units we would like to check.

required

Returns:

Type Description
Unit

The best unit to target.

Source code in cython_extensions/combat_utils.pyi
def cy_pick_enemy_target(enemies: Union[Units, list[Unit]]) -> Unit:
    """Pick the best thing to shoot at out of all enemies.

    Example:
    ```py
    from cython_extensions import cy_pick_enemy_target
    from sc2.units import Units
    from sc2.unit import Unit

    enemies: Units = self.enemy_units

    target: Unit = cy_pick_enemy_target(enemies)
    ```
    ```
    70.5 µs ± 818 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

    Python alternative:
    115 µs ± 766 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
    ```

    Args:
        enemies: All enemy units we would like to check.

    Returns:
        The best unit to target.
    """
    ...

cy_pylon_matrix_covers(position, pylons, height_grid, pylon_build_progress)

Check if a position is powered by a pylon.

Example:

from cython_functions import cy_pylon_matrix_covers
from sc2.position import Point2

# check if start location is powered by pylon
position: Point2 = self.start_location

can_place_structure_here: bool = cy_pylon_matrix_covers(
    position,
    self.structures(UnitTypeId.PYLON),
    self.game_info.terrain_height.data_numpy
)

1.85 µs ± 8.72 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Parameters:

Name Type Description Default
position Union[Point2, tuple[float, float]]

Position to check for power.

required
pylons Union[Units, list[Unit]]

The pylons we want to check.

required
height_grid ndarray

Height grid supplied from python-sc2 as a numpy array.

required
pylon_build_progress float

If less than 1.0, check near pending pylons. Default is 1.0.

required

Returns:

Type Description
bool

True if position has power, False otherwise.

Source code in cython_extensions/general_utils.pyi
def cy_pylon_matrix_covers(
    position: Union[Point2, tuple[float, float]],
    pylons: Union[Units, list[Unit]],
    height_grid: np.ndarray,
    pylon_build_progress: float,
) -> bool:
    """Check if a position is powered by a pylon.

    Example:
    ```py
    from cython_functions import cy_pylon_matrix_covers
    from sc2.position import Point2

    # check if start location is powered by pylon
    position: Point2 = self.start_location

    can_place_structure_here: bool = cy_pylon_matrix_covers(
        position,
        self.structures(UnitTypeId.PYLON),
        self.game_info.terrain_height.data_numpy
    )
    ```

    ```
    1.85 µs ± 8.72 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
    ```

    Args:
        position: Position to check for power.
        pylons: The pylons we want to check.
        height_grid: Height grid supplied from `python-sc2` as a numpy array.
        pylon_build_progress: If less than 1.0, check near pending pylons.
            Default is 1.0.

    Returns:
        True if `position` has power, False otherwise.

    """

cy_unit_pending(ai, unit_type)

Check how many unit_type are pending.

Faster unit specific alternative to python-sc2's already_pending

Example:

from cython_functions import cy_unit_pending
from sc2.ids.unit_typeid import UnitTypeId

num_marines_pending: int = cy_unit_pending(UnitTypeId.MARINE)
453 ns ± 9.35 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Python-sc2 `already_pending` alternative:
2.82 µs ± 29 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Parameters:

Name Type Description Default
ai BotAI

Bot object that will be running the game.

required
unit_type UnitTypeId

Unit type we want to check.

required

Returns:

Type Description
int

How many unit_type are currently building.

Source code in cython_extensions/general_utils.pyi
def cy_unit_pending(ai: "BotAI", unit_type: UnitID) -> int:
    """Check how many unit_type are pending.

    Faster unit specific alternative to `python-sc2`'s `already_pending`

    Example:
    ```py
    from cython_functions import cy_unit_pending
    from sc2.ids.unit_typeid import UnitTypeId

    num_marines_pending: int = cy_unit_pending(UnitTypeId.MARINE)
    ```
    ```
    453 ns ± 9.35 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

    Python-sc2 `already_pending` alternative:
    2.82 µs ± 29 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
    ```

    Args:
        ai: Bot object that will be running the game.
        unit_type: Unit type we want to check.

    Returns:
        How many unit_type are currently building.


    """
    ...

cy_angle_diff(a, b)

Absolute angle difference between 2 angles

Parameters:

Name Type Description Default
a float

First angle.

required
b float

Second angle.

required

Returns:

Name Type Description
angle_difference float

Difference between the two angles.

Source code in cython_extensions/geometry.pyi
def cy_angle_diff(a: float, b: float) -> float:
    """Absolute angle difference between 2 angles

    Args:
        a: First angle.
        b: Second angle.

    Returns:
        angle_difference: Difference between the two angles.
    """
    ...

cy_angle_to(from_pos, to_pos)

Angle from point to other point in radians

Parameters:

Name Type Description Default
from_pos Union[Point2, tuple[float, float]]

First 2D point.

required
to_pos Union[Point2, tuple[float, float]]

Measure angle to this 2D point.

required

Returns:

Name Type Description
angle float

Angle in radians.

Source code in cython_extensions/geometry.pyi
def cy_angle_to(
    from_pos: Union[Point2, tuple[float, float]],
    to_pos: Union[Point2, tuple[float, float]],
) -> float:
    """Angle from point to other point in radians

    Args:
        from_pos: First 2D point.
        to_pos: Measure angle to this 2D point.

    Returns:
        angle: Angle in radians.

    """
    ...

cy_distance_to(p1, p2)

Check distance between two Point2 positions.

Example:

from cython_functions import cy_distance_to

dist: float = cy_distance_to(
    self.start_location, self.game_info.map_center
)
cy_distance_to(Point2, Point2)
157 ns ± 2.69 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

cy_distance_to(unit1.position, unit2.position)
219 ns ± 10.5 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Python alternative:

Point1.distance_to(Point2)
386 ns ± 2.71 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

unit1.distance_to(unit2)
583 ns ± 7.89 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Parameters:

Name Type Description Default
p1 Union[Point2, tuple[float, float]]

First point.

required
p2 Union[Point2, tuple[float, float]]

Measure to this point.

required

Returns:

Name Type Description
distance float

Distance in tiles.

Source code in cython_extensions/geometry.pyi
def cy_distance_to(
    p1: Union[Point2, tuple[float, float]], p2: Union[Point2, tuple[float, float]]
) -> float:
    """Check distance between two Point2 positions.

    Example:
    ```py
    from cython_functions import cy_distance_to

    dist: float = cy_distance_to(
        self.start_location, self.game_info.map_center
    )
    ```
    ```
    cy_distance_to(Point2, Point2)
    157 ns ± 2.69 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

    cy_distance_to(unit1.position, unit2.position)
    219 ns ± 10.5 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

    Python alternative:

    Point1.distance_to(Point2)
    386 ns ± 2.71 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

    unit1.distance_to(unit2)
    583 ns ± 7.89 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
    ```

    Args:
        p1: First point.
        p2: Measure to this point.

    Returns:
        distance: Distance in tiles.


    """
    ...

cy_distance_to_squared(p1, p2)

Similar to cy_distance_to but without a square root operation. Use this for ~1.3x speedup

Example:

from cython_functions import cy_distance_to_squared

dist: float = cy_distance_to_squared(
    self.start_location, self.game_info.map_center
)

Parameters:

Name Type Description Default
p1 Union[Point2, tuple[float, float]]

First point.

required
p2 Union[Point2, tuple[float, float]]

Measure to this point.

required

Returns:

Name Type Description
distance float

Distance in tiles, squared.

Source code in cython_extensions/geometry.pyi
def cy_distance_to_squared(
    p1: Union[Point2, tuple[float, float]], p2: Union[Point2, tuple[float, float]]
) -> float:
    """Similar to `cy_distance_to` but without a square root operation.
    Use this for ~1.3x speedup

    Example:
    ```python
    from cython_functions import cy_distance_to_squared

    dist: float = cy_distance_to_squared(
        self.start_location, self.game_info.map_center
    )
    ```

    Args:
        p1: First point.
        p2: Measure to this point.

    Returns:
        distance: Distance in tiles, squared.
    """
    ...

cy_find_average_angle(start_point, reference_point, points)

Find the average angle between the points and the reference point.

Given a starting point, a reference point, and a list of points, find the average angle between the vectors from the starting point to the reference point and the starting point to the points.

Example:

from cython_extensions import cy_find_average_angle

angle: float = cy_get_angle_between_points(
    self.start_location,
    self.game_info.map_center,
    [w.position for w in self.workers]
)

Parameters:

Name Type Description Default
start_point Union[Point2, tuple[float, float]]

Origin for the vectors to the other given points.

required
reference_point Union[Point2, tuple[float, float]]

Vector forming one leg of the angle.

required
points list[Point2]

Points to calculate the angle between relative to the reference point.

required

Returns:

Type Description
float

Average angle in radians between the reference

float

point and the given points.

Source code in cython_extensions/geometry.pyi
def cy_find_average_angle(
    start_point: Union[Point2, tuple[float, float]],
    reference_point: Union[Point2, tuple[float, float]],
    points: list[Point2],
) -> float:
    """Find the average angle between the points and the reference point.

    Given a starting point, a reference point, and a list of points, find the average
    angle between the vectors from the starting point to the reference point and the
    starting point to the points.

    Example:
    ```py
    from cython_extensions import cy_find_average_angle

    angle: float = cy_get_angle_between_points(
        self.start_location,
        self.game_info.map_center,
        [w.position for w in self.workers]
    )
    ```

    Args:
        start_point: Origin for the vectors to the other given points.
        reference_point: Vector forming one leg of the angle.
        points: Points to calculate the angle between relative
            to the reference point.

    Returns:
        Average angle in radians between the reference
        point and the given points.

    """
    ...

cy_find_correct_line(points, base_location)

Given a list of points and a center point, find if there's a line such that all other points are above or below the line. Returns the line in the form Ax + By + C = 0 and the point that was used.

If no such line is found, it returns ((0, 0, 0), ).

Parameters:

Name Type Description Default
points list[Point2]

Points that need to be on one side of the line.

required
base_location Union[Point2, tuple[float, float]]

Starting point for the line.

required

Returns:

Type Description
tuple[float]

First element is the coefficients of Ax + By + C = 0.

tuple[float]

Second element is the point used to form the line.

Source code in cython_extensions/geometry.pyi
def cy_find_correct_line(
    points: list[Point2], base_location: Union[Point2, tuple[float, float]]
) -> tuple[tuple[float], tuple[float]]:
    """
    Given a list of points and a center point, find if there's a line such that all
    other points are above or below the line. Returns the line in the form
    Ax + By + C = 0 and the point that was used.

    If no such line is found, it returns ((0, 0, 0), <last_point_checked>).

    Args:
        points: Points that need to be on one side of the line.
        base_location: Starting point for the line.

    Returns:
        First element is the coefficients of Ax + By + C = 0.
        Second element is the point used to form the line.
    """
    ...

cy_get_angle_between_points(point_a, point_b)

Get the angle between two points as if they were vectors from the origin.

Example:

from cython_functions import cy_get_angle_between_points

angle: float = cy_get_angle_between_points(
    self.start_location, self.game_info.map_center
)

Parameters:

Name Type Description Default
point_a Union[Point2, tuple[float, float]]

First point.

required
point_b Union[Point2, tuple[float, float]]

Measure to this point.

required

Returns:

Type Description
float

The angle between the two points.

Source code in cython_extensions/geometry.pyi
def cy_get_angle_between_points(
    point_a: Union[Point2, tuple[float, float]],
    point_b: Union[Point2, tuple[float, float]],
) -> float:
    """Get the angle between two points as if they were vectors from the origin.

    Example:
    ```py
    from cython_functions import cy_get_angle_between_points

    angle: float = cy_get_angle_between_points(
        self.start_location, self.game_info.map_center
    )
    ```

    Args:
        point_a: First point.
        point_b: Measure to this point.

    Returns:
        The angle between the two points.
    """
    ...

cy_towards(start_pos, target_pos, distance)

Get position from start_pos towards target_pos based on distance.

Example:

from cython_functions import cy_towards

new_pos: Tuple[float, float] = cy_towards(
    self.start_location,
    self.game_info.map_center,
    12.0
)

Note: For performance reasons this returns the point2 as a tuple, if a python-sc2 Point2 is required it's up to the user to convert it.

Example:

new_pos: Point2 = Point2(
    cy_towards(
        self.start_location, self.enemy_start_locations, 10.0
    )
)

Though for best performance it is recommended to simply work with the tuple if possible:

new_pos: tuple[float, float] = cy_towards(
    self.start_location, self.enemy_start_locations, 10.0
)

191 ns ± 0.855 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

Python-sc2's `start_pos.towards(target_pos, distance)` alternative:
2.73 µs ± 18.9 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Parameters:

Name Type Description Default
start_pos Point2

Start from this 2D position.

required
target_pos Point2

Go towards this 2D position.

required
distance float

How far we go towards target_pos.

required

Returns:

Type Description
tuple[float, float]

The new position as a tuple of x and y coordinates.

Source code in cython_extensions/geometry.pyi
def cy_towards(
    start_pos: Point2, target_pos: Point2, distance: float
) -> tuple[float, float]:
    """Get position from start_pos towards target_pos based on distance.

    Example:
    ```py
    from cython_functions import cy_towards

    new_pos: Tuple[float, float] = cy_towards(
        self.start_location,
        self.game_info.map_center,
        12.0
    )
    ```

    Note: For performance reasons this returns the point2 as a tuple, if a
    python-sc2 Point2 is required it's up to the user to convert it.

    Example:
    ```py
    new_pos: Point2 = Point2(
        cy_towards(
            self.start_location, self.enemy_start_locations, 10.0
        )
    )
    ```

    Though for best performance it is recommended to simply work with the tuple if possible:
    ```py
    new_pos: tuple[float, float] = cy_towards(
        self.start_location, self.enemy_start_locations, 10.0
    )
    ```

    ```
    191 ns ± 0.855 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

    Python-sc2's `start_pos.towards(target_pos, distance)` alternative:
    2.73 µs ± 18.9 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
    ```


    Args:
        start_pos: Start from this 2D position.
        target_pos: Go towards this 2D position.
        distance: How far we go towards target_pos.

    Returns:
        The new position as a tuple of x and y coordinates.
    """
    ...

cy_translate_point_along_line(point, a_value, distance)

Translates a point along a line defined by a slope value.

This function moves a given point along a line in a direction determined by the slope a_value, by a specified distance. The new point after translation is returned.

Parameters:

Name Type Description Default
point Union[Point2, tuple[float, float]]

The point to be translated, given as either a Point2

required
a_value float

The slope of the line along which the point will be moved.

required
distance float

The distance to move the point along the line.

required

Returns:

Type Description
float

A tuple representing the new position of the point

float

after translation.

Source code in cython_extensions/geometry.pyi
def cy_translate_point_along_line(
    point: Union[Point2, tuple[float, float]], a_value: float, distance: float
) -> tuple[float, float]:
    """
    Translates a point along a line defined by a slope value.

    This function moves a given point along a line in a direction
    determined by the slope `a_value`, by a specified `distance`.
    The new point after translation is returned.

    Args:
        point: The point to be translated, given as either a `Point2`
        object or a tuple of `(x, y)` coordinates.
        a_value: The slope of the line along which the point will be moved.
        distance: The distance to move the point along the line.

    Returns:
        A tuple representing the new position of the point
        after translation.
    """
    ...

cy_flood_fill_grid(start_point, terrain_grid, pathing_grid, max_distance, cutoff_points)

Given a set of coordinates, draw a box that fits all the points.

Example:

from cython_extensions import cy_flood_fill_grid

all_points = terrain_flood_fill(
    start_point=self.start_location.rounded,
    terrain_grid=self.game_info.terrain_height.data_numpy.T,
    pathing_grid=self.game_info.pathing_grid.data_numpy.T,
    max_distance=40,
    choke_points={}
)

Parameters

start_point : Start algorithm from here. terrain_grid : Numpy array containing heights for the map. pathing_grid : Numpy array containing pathing values for the map. max_distance : The maximum distance the flood fill should reach before halting. cutoff_points : Points which we don't want the algorithm to pass. Choke points are a good use case.

Returns

tuple of tuple of float : A pair of coordinates that determine the box in the following format: ((xmin, xmax), (ymin, ymax))

Source code in cython_extensions/map_analysis.pyi
def cy_flood_fill_grid(
    start_point: Union[Point2, tuple],
    terrain_grid: np.ndarray,
    pathing_grid: np.ndarray,
    max_distance: int,
    cutoff_points: set,
) -> set[tuple]:
    """Given a set of coordinates, draw a box that fits
    all the points.

    Example:
    ```py
    from cython_extensions import cy_flood_fill_grid

    all_points = terrain_flood_fill(
        start_point=self.start_location.rounded,
        terrain_grid=self.game_info.terrain_height.data_numpy.T,
        pathing_grid=self.game_info.pathing_grid.data_numpy.T,
        max_distance=40,
        choke_points={}
    )

    ```

    Parameters
    ----------
    start_point : Start algorithm from here.
    terrain_grid : Numpy array containing heights for the map.
    pathing_grid : Numpy array containing pathing values for the map.
    max_distance : The maximum distance the flood fill should reach before halting.
    cutoff_points : Points which we don't want the algorithm to pass.
    Choke points are a good use case.

    Returns
    -------
    tuple of tuple of float :
        A pair of coordinates that determine the box in the following format:
        ((xmin, xmax), (ymin, ymax))

    """
    ...

cy_get_bounding_box(coordinates)

Given a set of coordinates, draw a box that fits all the points.

Example:

from cython_extensions import cy_get_bounding_box

points: set[Point2] = {w.position for w in self.workers}
raw_x_bounds, raw_y_bounds = cy_get_bounding_box(points)

Parameters:

Name Type Description Default
coordinates set[Point2]

The points around which the bounding box should be drawn.

required

Returns:

Type Description
tuple[float, float]

A tuple containing two tuples:

tuple[float, float]
  • The first tuple represents the minimum and maximum x values
tuple[tuple[float, float], tuple[float, float]]

(xmin, xmax).

tuple[tuple[float, float], tuple[float, float]]
  • The second tuple represents the minimum and maximum y values
tuple[tuple[float, float], tuple[float, float]]

(ymin, ymax).

Source code in cython_extensions/map_analysis.pyi
def cy_get_bounding_box(
    coordinates: set[Point2],
) -> tuple[tuple[float, float], tuple[float, float]]:
    """Given a set of coordinates, draw a box that fits
    all the points.

    Example:
    ```py
    from cython_extensions import cy_get_bounding_box

    points: set[Point2] = {w.position for w in self.workers}
    raw_x_bounds, raw_y_bounds = cy_get_bounding_box(points)

    ```

    Args:
        coordinates:
            The points around which the bounding box should be drawn.

    Returns:
        A tuple containing two tuples:
        - The first tuple represents the minimum and maximum x values
        (xmin, xmax).
        - The second tuple represents the minimum and maximum y values
        (ymin, ymax).

    """
    ...

cy_all_points_below_max_value(grid, value, points_to_check)

Check points on grid, and return True if they are all below value.

Example:

from cython_extensions import cy_all_points_below_max_value

# pretend grid has enemy influence added
grid = self.game_info.pathing_grid.data_numpy.T
all_safe: bool = cy_all_points_below_max_value(
    grid, 1.0, [self.start_location.rounded]
)

Parameters:

Name Type Description Default
grid ndarray

The grid to check.

required
value float

The max value.

required
points_to_check list[tuple[int, int]]

List of points we are checking.

required

Returns:

Type Description
bool

Are all points_to_check below value?

Source code in cython_extensions/numpy_helper.pyi
def cy_all_points_below_max_value(
    grid: np.ndarray, value: float, points_to_check: list[tuple[int, int]]
) -> bool:
    """Check points on grid, and return True if they are all below
    `value`.

    Example:
    ```py
    from cython_extensions import cy_all_points_below_max_value

    # pretend grid has enemy influence added
    grid = self.game_info.pathing_grid.data_numpy.T
    all_safe: bool = cy_all_points_below_max_value(
        grid, 1.0, [self.start_location.rounded]
    )

    ```

    Parameters:
        grid: The grid to check.
        value: The max value.
        points_to_check: List of points we are checking.

    Returns:
        Are all points_to_check below value?


    """
    ...

cy_all_points_have_value(grid, value, points)

Check points on grid, and return True if they are all equal value.

Example:

from cython_extensions import cy_all_points_have_value

# pretend grid has enemy influence added
grid = self.game_info.pathing_grid.data_numpy.T
all_safe: bool = cy_all_points_have_value(
    grid, 1.0, [self.start_location.rounded]
)

Parameters:

Name Type Description Default
grid ndarray

The grid to check.

required
value float

The max value.

required
points list[tuple[int, int]]

List of points we are checking.

required

Returns:

Type Description
bool

Are all points equal value?

Source code in cython_extensions/numpy_helper.pyi
def cy_all_points_have_value(
    grid: np.ndarray, value: float, points: list[tuple[int, int]]
) -> bool:
    """Check points on grid, and return True if they are all equal
    `value`.

    Example:
    ```py
    from cython_extensions import cy_all_points_have_value

    # pretend grid has enemy influence added
    grid = self.game_info.pathing_grid.data_numpy.T
    all_safe: bool = cy_all_points_have_value(
        grid, 1.0, [self.start_location.rounded]
    )

    ```

    Parameters:
        grid: The grid to check.
        value: The max value.
        points: List of points we are checking.

    Returns:
        Are all points equal value?

    """
    ...

cy_last_index_with_value(grid, value, points)

Finds the last index with the matching value, stopping as soon as a value doesn't match. Returns -1 if points is empty or the first value doesn't match

Example:

from cython_extensions import cy_last_index_with_value

grid = self.game_info.pathing_grid.data_numpy.T
points: list[Point2] = [w.position.rounded for w in self.workers]
last_pathable_index = cy_last_index_with_value(grid, 1, points)

Parameters:

Name Type Description Default
grid ndarray

The grid to check points on.

required
value int

The value we are looking for.

required
points list[tuple[int, int]]

Points we want to check

required

Returns:

Type Description
int

The last index in points that has value

Source code in cython_extensions/numpy_helper.pyi
def cy_last_index_with_value(
    grid: np.ndarray, value: int, points: list[tuple[int, int]]
) -> int:
    """Finds the last index with the matching value, stopping as soon as a
    value doesn't match.
    Returns -1 if points is empty or the first value doesn't match

    Example:
    ```py
    from cython_extensions import cy_last_index_with_value

    grid = self.game_info.pathing_grid.data_numpy.T
    points: list[Point2] = [w.position.rounded for w in self.workers]
    last_pathable_index = cy_last_index_with_value(grid, 1, points)

    ```

    Parameters:
        grid: The grid to check `points` on.
        value: The value we are looking for.
        points: Points we want to check

    Returns:
        The last index in `points` that has `value`

    """
    ...

cy_point_below_value(grid, position, weight_safety_limit=1.0)

Check a position on a 2D grid. Is it below weight_safety_limit? Useful for checking enemy influence on a position.

Example:

from cython_extensions import cy_point_below_value

# pretend grid has enemy influence added
grid = self.game_info.pathing_grid.data_numpy.T
safe: bool = cy_point_below_value(grid, self.start_location.rounded)

987 ns ± 10.1 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Python alternative:
4.66 µs ± 64.8 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Parameters:

Name Type Description Default
grid ndarray

The grid to check.

required
position tuple[int, int]

2D coordinate to check on grid.

required
weight_safety_limit float

(default = 1.0) We want to check if the point is less than or equal to this.

1.0

Returns:

Type Description
bool

The last index in points that has value.

Source code in cython_extensions/numpy_helper.pyi
def cy_point_below_value(
    grid: np.ndarray, position: tuple[int, int], weight_safety_limit: float = 1.0
) -> bool:
    """Check a position on a 2D grid.
    Is it below `weight_safety_limit`?
    Useful for checking enemy influence on a position.

    Example:
    ```py
    from cython_extensions import cy_point_below_value

    # pretend grid has enemy influence added
    grid = self.game_info.pathing_grid.data_numpy.T
    safe: bool = cy_point_below_value(grid, self.start_location.rounded)
    ```

    ```
    987 ns ± 10.1 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

    Python alternative:
    4.66 µs ± 64.8 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
    ```

    Parameters:
        grid: The grid to check.
        position: 2D coordinate to check on grid.
        weight_safety_limit: (default = 1.0) We want to check
            if the point is less than or equal to this.

    Returns:
        The last index in `points` that has `value`.

    """
    ...

cy_points_with_value(grid, value, points)

Check points on grid, and return those that equal value

Example:

from cython_extensions import cy_points_with_value
import numpy as np

# pretend grid has enemy influence added
grid: np.ndarray = self.game_info.pathing_grid.data_numpy.T
safe: bool = cy_points_with_value(
    grid, 1.0, [self.start_location.rounded]
)

Parameters:

Name Type Description Default
grid ndarray

The grid to check.

required
value float

2D coordinate to check on grid.

required
points list[tuple[int, int]]

List of points we are checking.

required

Returns:

Type Description
list[tuple[int, int]]

All points that equal value on grid.

Source code in cython_extensions/numpy_helper.pyi
def cy_points_with_value(
    grid: np.ndarray, value: float, points: list[tuple[int, int]]
) -> list[tuple[int, int]]:
    """Check points on grid, and return those that equal `value`

    Example:
    ```py
    from cython_extensions import cy_points_with_value
    import numpy as np

    # pretend grid has enemy influence added
    grid: np.ndarray = self.game_info.pathing_grid.data_numpy.T
    safe: bool = cy_points_with_value(
        grid, 1.0, [self.start_location.rounded]
    )

    ```

    Parameters:
        grid: The grid to check.
        value: 2D coordinate to check on grid.
        points: List of points we are checking.

    Returns:
        All points that equal `value` on grid.

    """
    ...

cy_can_place_structure(building_origin, building_size, creep_grid, placement_grid, pathing_grid, avoid_creep=True, include_addon=False)

Simulate whether a structure can be placed at building_origin Fast alternative to python-sc2 can_place

Example:

from cython_extensions import cy_can_place_structure

can_place: bool = cy_can_place_structure(
    (155, 45),
    (3, 3),
    self.ai.state.creep.data_numpy,
    self.ai.game_info.placement_grid.data_numpy,
    self.ai.game_info.pathing_grid.data_numpy,
    avoid_creep=self.race != Race.Zerg,
    include_addon=False,
)

1.21 µs ± 891 ns per loop (mean ± std. dev. of 1000 runs, 10 loops each)

Parameters:

Name Type Description Default
building_origin tuple[int, int]

The top left corner of the intended structure.

required
building_size tuple[int, int]

For example: (3, 3) for barracks. (2, 2) for depot, (5, 5) for command center.

required
creep_grid ndarray

Creep grid.

required
placement_grid ndarray
required
pathing_grid ndarray
required
avoid_creep bool

Ensure this is False if checking Zerg structures.

True
include_addon bool

Check if there is room for addon too.

False

Returns:

Type Description
bool

Can we place structure at building_origin?

Source code in cython_extensions/placement_solver.pyi
def cy_can_place_structure(
    building_origin: tuple[int, int],
    building_size: tuple[int, int],
    creep_grid: np.ndarray,
    placement_grid: np.ndarray,
    pathing_grid: np.ndarray,
    avoid_creep: bool = True,
    include_addon: bool = False,
) -> bool:
    """Simulate whether a structure can be placed at `building_origin`
    Fast alternative to python-sc2 `can_place`

    Example:
    ```py
    from cython_extensions import cy_can_place_structure

    can_place: bool = cy_can_place_structure(
        (155, 45),
        (3, 3),
        self.ai.state.creep.data_numpy,
        self.ai.game_info.placement_grid.data_numpy,
        self.ai.game_info.pathing_grid.data_numpy,
        avoid_creep=self.race != Race.Zerg,
        include_addon=False,
    )
    ```

    ```
    1.21 µs ± 891 ns per loop (mean ± std. dev. of 1000 runs, 10 loops each)
    ```

    Parameters:
        building_origin: The top left corner of the intended structure.
        building_size: For example: (3, 3) for barracks.
            (2, 2) for depot,
            (5, 5) for command center.
        creep_grid: Creep grid.
        placement_grid:
        pathing_grid:
        avoid_creep: Ensure this is False if checking Zerg structures.
        include_addon: Check if there is room for addon too.

    Returns:
        Can we place structure at building_origin?


    """
    ...

cy_find_building_locations(kernel, x_stride, y_stride, x_bounds, y_bounds, creep_grid, placement_grid, pathing_grid, points_to_avoid_grid, avoid_creep=True, include_addon=False)

Use a convolution pass to find all possible building locations in an area Check ares-sc2 for a full example of using this to calculate building formations.

https://github.com/AresSC2/ares-sc2/blob/main/src/ares/managers/placement_manager.py

Example:

from cython_extensions import cy_find_building_locations

# find 3x3 locations, making room for addons.
# check out map_analysis.cy_get_bounding_box to calculate
# raw_x_bounds and raw_x_bounds
three_by_three_positions = cy_find_building_locations(
    kernel=np.ones((5, 3), dtype=np.uint8),
    x_stride=5,
    y_stride=3,
    x_bounds=raw_x_bounds,
    y_bounds=raw_y_bounds,
    creep_grid=creep_grid,
    placement_grid=placement_grid,
    pathing_grid=pathing_grid,
    points_to_avoid_grid=self.points_to_avoid_grid,
    building_width=3,
    building_height=3,
    avoid_creep=True
)

64.8 µs ± 4.05 µs per loop (mean ± std. dev. of 1000 runs, 10 loops each)

Parameters:

Name Type Description Default
kernel ndarray

The size of the sliding window that scans this area.

required
x_stride int

The x distance the kernel window moves each step.

required
y_stride int

The y distance the kernel window moves downwards.

required
x_bounds tuple[int, int]

The starting point of the algorithm.

required
y_bounds tuple[int, int]

The end point of the algorithm.

required
creep_grid ndarray
required
placement_grid ndarray
required
pathing_grid ndarray
required
points_to_avoid_grid ndarray

Grid containing 1s where we shouldn't place anything.

required
avoid_creep bool

Ensure this is False if checking Zerg structures.

True
include_addon bool

Check if there is room for addon too.

False

Returns:

Type Description
list[tuple[float, float]]

Final list of positions that make up the building formation.

Source code in cython_extensions/placement_solver.pyi
def cy_find_building_locations(
    kernel: np.ndarray,
    x_stride: int,
    y_stride: int,
    x_bounds: tuple[int, int],
    y_bounds: tuple[int, int],
    creep_grid: np.ndarray,
    placement_grid: np.ndarray,
    pathing_grid: np.ndarray,
    points_to_avoid_grid: np.ndarray,
    avoid_creep: bool = True,
    include_addon: bool = False,
) -> list[tuple[float, float]]:
    """Use a convolution pass to find all possible building locations in an area
    Check `ares-sc2` for a full example of using this to calculate
    building formations.

    https://github.com/AresSC2/ares-sc2/blob/main/src/ares/managers/placement_manager.py

    Example:
    ```py
    from cython_extensions import cy_find_building_locations

    # find 3x3 locations, making room for addons.
    # check out map_analysis.cy_get_bounding_box to calculate
    # raw_x_bounds and raw_x_bounds
    three_by_three_positions = cy_find_building_locations(
        kernel=np.ones((5, 3), dtype=np.uint8),
        x_stride=5,
        y_stride=3,
        x_bounds=raw_x_bounds,
        y_bounds=raw_y_bounds,
        creep_grid=creep_grid,
        placement_grid=placement_grid,
        pathing_grid=pathing_grid,
        points_to_avoid_grid=self.points_to_avoid_grid,
        building_width=3,
        building_height=3,
        avoid_creep=True
    )

    ```

    ```
    64.8 µs ± 4.05 µs per loop (mean ± std. dev. of 1000 runs, 10 loops each)
    ```

    Parameters:
        kernel: The size of the sliding window that scans this area.
        x_stride: The x distance the kernel window moves each step.
        y_stride: The y distance the kernel window moves downwards.
        x_bounds: The starting point of the algorithm.
        y_bounds: The end point of the algorithm.
        creep_grid:
        placement_grid:
        pathing_grid:
        points_to_avoid_grid: Grid containing `1`s where we shouldn't
            place anything.
        avoid_creep: Ensure this is False if checking Zerg structures.
        include_addon: Check if there is room for addon too.

    Returns:
        Final list of positions that make up the building formation.


    """
    ...

cy_center(units)

Given some units, find the center point.

Example:

from ares.cython_functions.units_utils import cy_center

centroid: Tuple[float, float] = cy_center(self.workers)

# centroid_point2 = Point2(centroid)

54.2 µs ± 137 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

`python-sc2`'s `units.center` alternative:
107 µs ± 255 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Parameters:

Name Type Description Default
units Union[Units, list[Unit]]

Units we want to check

required

Returns:

Type Description
tuple[float, float]

Centroid of all units positions

Source code in cython_extensions/units_utils.pyi
def cy_center(units: Union[Units, list[Unit]]) -> tuple[float, float]:
    """Given some units, find the center point.


    Example:
    ```py
    from ares.cython_functions.units_utils import cy_center

    centroid: Tuple[float, float] = cy_center(self.workers)

    # centroid_point2 = Point2(centroid)
    ```

    ```
    54.2 µs ± 137 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

    `python-sc2`'s `units.center` alternative:
    107 µs ± 255 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
    ```

    Parameters:
        units: Units we want to check

    Returns:
        Centroid of all units positions

    """
    ...

cy_closest_to(position, units)

Iterate through units to find closest to position.

Example:

from cython_functions import cy_closest_to
from sc2.unit import Unit

closest_unit: Unit = cy_closest_to(self.start_location, self.workers)

14.3 µs ± 135 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

python-sc2's `units.closest_to()` alternative:
98.9 µs ± 240 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

If using python-sc2's `units.closest_to(Point2):
200 µs ± 1.02 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

Parameters:

Name Type Description Default
position Union[Point2, tuple[float, float]]

Position to measure distance from.

required
units Union[Units, list[Unit]]

Collection of units we want to check.

required

Returns:

Type Description
Unit

Unit closest to position.

Source code in cython_extensions/units_utils.pyi
def cy_closest_to(
    position: Union[Point2, tuple[float, float]], units: Union[Units, list[Unit]]
) -> Unit:
    """Iterate through `units` to find closest to `position`.

    Example:
    ```py
    from cython_functions import cy_closest_to
    from sc2.unit import Unit

    closest_unit: Unit = cy_closest_to(self.start_location, self.workers)
    ```

    ```
    14.3 µs ± 135 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

    python-sc2's `units.closest_to()` alternative:
    98.9 µs ± 240 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

    If using python-sc2's `units.closest_to(Point2):
    200 µs ± 1.02 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
    ```

    Parameters:
        position: Position to measure distance from.
        units: Collection of units we want to check.

    Returns:
        Unit closest to `position`.

    """
    ...

cy_find_units_center_mass(units, distance)

Given some units, find the center mass

Example:

from cython_functions import cy_find_units_center_mass
from sc2.position import Point2

center_mass: Point2
num_units: int
center_mass, num_units = cy_find_units_center_mass(self.units, 10.0)

47.8 ms ± 674 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

python alternative:
322 ms ± 5.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Parameters:

Name Type Description Default
units Union[Units, list[Unit]]

Collection of units we want to check.

required
distance float

The distance to check from the center mass.

required

Returns:

Type Description
tuple[tuple[float, float], int]

The center mass, and how many units are within distance of the center mass.

Source code in cython_extensions/units_utils.pyi
def cy_find_units_center_mass(units: Union[Units, list[Unit]], distance: float) -> tuple[tuple[float, float], int]:
    """Given some units, find the center mass

    Example:
    ```py
    from cython_functions import cy_find_units_center_mass
    from sc2.position import Point2

    center_mass: Point2
    num_units: int
    center_mass, num_units = cy_find_units_center_mass(self.units, 10.0)
    ```

    ```
    47.8 ms ± 674 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

    python alternative:
    322 ms ± 5.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    ```

    Parameters:
        units: Collection of units we want to check.
        distance: The distance to check from the center mass.

    Returns:
        The center mass, and how many units are within `distance` of the center mass.
    """
    ...

cy_in_attack_range(unit, units, bonus_distance=0.0)

Find all units that unit can shoot at.

Doesn't check if the unit weapon is ready. See: cython_functions.attack_ready

Example:

from cython_functions import cy_in_attack_range
from sc2.unit import Unit

in_attack_range: list[Unit] = cy_in_attack_range(self.workers[0], self.enemy_units)

7.28 µs ± 26.3 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

python-sc2's `units.in_attack_range_of(unit)` alternative:
30.4 µs ± 271 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Parameters:

Name Type Description Default
unit Unit

The unit to measure distance from.

required
units Union[Units, list[Unit]]

Collection of units we want to check.

required
bonus_distance float

Additional distance to consider.

0.0

Returns:

Type Description
list[Unit]

Units that are in attack range of unit.

Source code in cython_extensions/units_utils.pyi
def cy_in_attack_range(
    unit: Unit, units: Union[Units, list[Unit]], bonus_distance: float = 0.0
) -> list[Unit]:
    """Find all units that unit can shoot at.

    Doesn't check if the unit weapon is ready. See:
    `cython_functions.attack_ready`

    Example:
    ```py
    from cython_functions import cy_in_attack_range
    from sc2.unit import Unit

    in_attack_range: list[Unit] = cy_in_attack_range(self.workers[0], self.enemy_units)
    ```

    ```
    7.28 µs ± 26.3 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

    python-sc2's `units.in_attack_range_of(unit)` alternative:
    30.4 µs ± 271 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
    ```

    Parameters:
        unit: The unit to measure distance from.
        units: Collection of units we want to check.
        bonus_distance: Additional distance to consider.

    Returns:
        Units that are in attack range of `unit`.

    """
    ...

cy_sorted_by_distance_to(units, position, reverse=False)

Sort units by distance to position

Example:

from cython_functions import cy_sorted_by_distance_to
from sc2.unit import Unit

sorted_by_distance: list[Unit] = cy_sorted_by_distance_to(
    self.workers, self.start_location
)

33.7 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

python-sc2's `units.sorted_by_distance_to(position)` alternative:
246 µs ± 830 ns per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

Parameters:

Name Type Description Default
units Union[Units, list[Unit]]

Units we want to sort.

required
position Point2

Sort by distance to this position.

required
reverse bool

Not currently used.

False

Returns:

Type Description
list[Unit]

Units sorted by distance to position.

Source code in cython_extensions/units_utils.pyi
def cy_sorted_by_distance_to(
    units: Union[Units, list[Unit]], position: Point2, reverse: bool = False
) -> list[Unit]:
    """Sort units by distance to `position`

    Example:
    ```py
    from cython_functions import cy_sorted_by_distance_to
    from sc2.unit import Unit

    sorted_by_distance: list[Unit] = cy_sorted_by_distance_to(
        self.workers, self.start_location
    )
    ```

    ```
    33.7 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

    python-sc2's `units.sorted_by_distance_to(position)` alternative:
    246 µs ± 830 ns per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
    ```

    Parameters:
        units: Units we want to sort.
        position: Sort by distance to this position.
        reverse: Not currently used.

    Returns:
        Units sorted by distance to position.

    """
    ...