예제 #1
0
class StigmergyAnt(Actor):

    COLOUR: Colour = Colour(255, 255, 0)
    ALT_COLOUR: Colour = Colour(255, 128, 0)
    ID: str = "sant"
    DROP_CHANCE: float = 0.5

    @staticmethod
    def create(kinds: [Kind] = []) -> Actor:
        return StigmergyAnt(kinds)

    @staticmethod
    def get_id() -> str:
        return StigmergyAnt.ID

    def get_colour(self) -> Colour:
        return StigmergyAnt.COLOUR if self.carrying is None else StigmergyAnt.ALT_COLOUR

    def food_interaction(self, loc: Location, world: World,
                         position: Position):
        if self.carrying is not None and Random().random(
        ) < StigmergyAnt.DROP_CHANCE:
            for l in world.get_adjacent_locations(position.x, position.y, 1,
                                                  False):
                if len(l.get_objects(Kind.FOOD)) > 0:
                    loc.add_object(self.carrying)
                    self.carrying = None
                    break
        else:
            food_items: [Object] = loc.get_objects(Kind.FOOD)
            if len(food_items) > 0:
                self.carrying = food_items[0]
                loc.remove_object(self.carrying)

    def move(self, frm: Location, to: Location):
        to.set_actor(self)
        to.add_object(TestObject())
        frm.remove_actor()

    def tick(self, world: World, elapsed: float, location: Location,
             position: Position):
        try:
            # COMPLETELY RANDOM CHOICES FOR WALKS (no preserving of direction).
            to: Location = Random().choice(
                world.get_adjacent_locations(position.x, position.y, 1, True))
            self.move(location, to)
            self.food_interaction(to, world, position)
        except IndexError:
            # The ant can't find anywhere to move...
            location.add_object(TestObject())

    def __init__(self, kinds: [Kind]):
        super().__init__(kinds)
        self.carrying: Object = None
예제 #2
0
class Nest(Ground):

    ID: str = "Nest"
    IS_PASSABLE: bool = True
    COLOUR: Colour = Colour(255, 255, 255)

    @staticmethod
    def create(kinds: [Kind] = []) -> Ground:
        return Nest(kinds)


    @staticmethod
    def get_id() -> str:
        return(Nest.ID)


    def is_passable(self) -> bool:
        return Nest.IS_PASSABLE


    def get_kinds(self) -> [Kind]:
        return self.kinds


    def add_kind(self, kind: Kind):
        self.kinds.append(kind)


    @staticmethod
    def get_colour() -> Colour:
        return Nest.COLOUR


    def __init__(self, kinds: [Kind]):
        super().__init__(kinds)
예제 #3
0
class Wall(Ground):

    IS_PASSABLE: bool = False
    ID: str = "Wall"
    COLOUR: Colour = Colour(0, 0, 0)

    @staticmethod
    def create(kinds: [Kind] = []) -> Ground:
        return Wall(kinds)

    @staticmethod
    def get_id() -> str:
        return (Wall.ID)

    def is_passable(self) -> bool:
        return Wall.IS_PASSABLE

    def get_kinds(self) -> [Kind]:
        return self.kinds

    def add_kind(self, kind: Kind):
        self.kinds.append(kind)

    @staticmethod
    def get_colour() -> Colour:
        return Wall.COLOUR

    def __init__(self, kinds: [Kind]):
        super().__init__(kinds)
예제 #4
0
class ModularQueen(ModularAnt):
    QUEEN_LOGICAL_AGE: float = 25
    BROOD_PHEROMONES_PER_TICK: int = 20
    HOLD_POSITION_CHANCE: float = 0.7
    COLOUR: Colour = Colour(255, 32, 240)
    ID: str = "mqu"

    @staticmethod
    def create(attributes: Attributes = None, kinds: [Kind] = []) -> Actor:
        return ModularQueen(attributes, kinds)


    @staticmethod
    def get_id() -> str:
        return ModularQueen.ID


    def get_colour(self) -> Colour:
        return ModularQueen.COLOUR


    def get_attributes_string(self) -> str:
        return repr([("age", self.age), ("bias", self.bias), ("holdness", self.holdness),
                ("wobble", self.wobble), ("hold_position_chance", self.hold_position_chance),
                     ("brood_position", self.brood_position)])


    def tick(self, world: World, elapsed: float, location: Location, position: Position):
        if self.brood_position is None:
            self.brood_position = (position.x, position.y)
            self.current_wander_behaviour.set_brood_position(self.brood_position)
        # location.add_brood_pheromones(ModularQueen.BROOD_PHEROMONES_PER_TICK)
        for loc in world.get_adjacent_locations(position.x, position.y, 1, False):
            loc.add_brood_pheromones(ModularQueen.BROOD_PHEROMONES_PER_TICK)
        self.current_wander_behaviour.set_age(ModularQueen.QUEEN_LOGICAL_AGE)
        super().tick(world, elapsed, location, position)


    def __init__(self, attributes: Union[Attributes, None], kinds: [Kind]):
        if Kind.QUEEN not in kinds:
            kinds.append(Kind.QUEEN)
        super().__init__(attributes, kinds)
        self.brood_position: Union[Position, None] = None
        self.hold_position_chance: float = ModularQueen.HOLD_POSITION_CHANCE
        attributes.set_for(self)
        self.current_wander_behaviour = QueenWanderPheromoneBehaviour(self.bias, self.holdness, self.wobble)
        if self.brood_position is not None:
            self.current_wander_behaviour.set_brood_position(self.brood_position)
예제 #5
0
class Cleaner(Ant):
    ID: str = "Cleaner"
    COLOUR: Colour = Colour(0, 100, 0)

    @staticmethod
    def create(kinds: [Kind] = []) -> Actor:
        return Cleaner(kinds)

    @staticmethod
    def get_id() -> str:
        return Cleaner.ID

    @staticmethod
    def get_colour() -> Colour:
        return Cleaner.COLOUR

    def __init__(self, kinds: [Kind]):
        super().__init__(kinds)
예제 #6
0
class Nurse(Ant):
    ID: str = "Nurse"
    COLOUR: Colour = Colour(0, 0, 205)

    @staticmethod
    def create(kinds: [Kind] = []) -> Actor:
        return Nurse(kinds)

    @staticmethod
    def get_id() -> str:
        return Nurse.ID

    @staticmethod
    def get_colour() -> Colour:
        return Nurse.COLOUR

    def __init__(self, kinds: [Kind]):
        super().__init__(kinds)
예제 #7
0
class Queen(Ant):
    ID: str = "Queen"
    COLOUR: Colour = Colour(75, 0, 130)

    @staticmethod
    def create(kinds: [Kind] = []) -> Actor:
        return Queen(kinds)

    @staticmethod
    def get_id() -> str:
        return Queen.ID

    @staticmethod
    def get_colour() -> Colour:
        return Queen.COLOUR

    def __init__(self, kinds: [Kind]):
        super().__init__(kinds)
예제 #8
0
class Forager(Ant):
    ID: str = "Forager"
    COLOUR: Colour = Colour(255, 128, 0)

    @staticmethod
    def create(kinds: [Kind] = []) -> Actor:
        return Forager(kinds)

    @staticmethod
    def get_id() -> str:
        return Forager.ID

    @staticmethod
    def get_colour() -> Colour:
        return Forager.COLOUR

    def __init__(self, kinds: [Kind]):
        super().__init__(kinds)
예제 #9
0
class BehaviourAnt(Actor):

    age: float = 0
    COLOUR: Colour = Colour(255, 255, 0)
    ID: str = "bant"
    DROP_CHANCE: float = 0.5

    @staticmethod
    def create(kinds: [Kind] = []) -> Actor:
        return BehaviourAnt(kinds)

    @staticmethod
    def get_id() -> str:
        return BehaviourAnt.ID

    def get_colour(self) -> Colour:
        if self.age > 200:
            return Colour(255, 255, 0)
        else:
            return Colour(255, self.age, 255 - self.age)

    def move(self, frm: Location, to: Location):
        to.set_actor(self)
        frm.remove_actor()

    def tick(self, world: World, elapsed: float, location: Location,
             position: Position):
        self.current_behaviour: Behaviour = WanderBehaviour(
            0, 1 - (self.age / 300), 1 - (self.age / 600))
        self.current_behaviour.do(world, elapsed, location, position, self)
        location.add_object(TestObject())
        self.age += 0.1 * elapsed

    def __init__(self, kinds: [Kind]):
        super().__init__(kinds)
        self.carrying: Object = None
        self.current_behaviour: Behaviour = WanderBehaviour()
        self.previous_behaviour: Behaviour = WanderBehaviour()
        self.age = 200 * random.random()
        self.current_dir = random.choice([[-1, -1], [-1, 0], [0, -1], [1, 1],
                                          [1, 0], [0, 1], [-1, 1], [1, -1]])
예제 #10
0
 def get_pheromone_colour(self) -> Colour:
     return Colour(
         int(Colour.MAX_VALUE *
             (float(self.get_foraging_pheromone_count()) /
              Location.PHEROMONE_COUNT_CAP)), 200, 200)
예제 #11
0
class Location(ABC):
    """\
The Location class. Manages a location in a map.
    """

    DEFAULT_OBJECTS_BASE_COLOUR: Colour = Colour(1, 0, 0)
    MIN_COLOUR_VAL: int = 50
    PHEROMONE_DETERIORATION_CHANCE: float = 0.05
    COMPLEX_PHEROMONE_DETERIORATION_CHANCE: float = 0.015
    PHEROMONE_COUNT_CAP: int = 20
    PHEROMONE_COLOUR: Colour = Colour(90, 127, 0)

    def __init__(self, ground: Ground):
        self.ground: Ground = ground  # 1..1
        self.actor: Union[Actor, None] = None  # 0..1
        self.objects: [Object] = []  # 0..*
        # General ant pheromones:
        self.pheromones: int = 0
        # More complex pheromones:
        # For carrying food.
        self.foraging_pheromones: int = 0
        # For staying near the nest.
        self.brood_pheromones: int = 0

    def add_pheromones(self, num: int):
        if self.pheromones < Location.PHEROMONE_COUNT_CAP and self.get_ground(
        ).is_passable():
            self.pheromones += num

    def get_pheromone_count(self):
        return self.pheromones

    def add_foraging_pheromones(self, num: int):
        if self.foraging_pheromones < Location.PHEROMONE_COUNT_CAP and self.get_ground(
        ).is_passable():
            self.foraging_pheromones += num

    def get_foraging_pheromone_count(self):
        return self.foraging_pheromones

    def add_brood_pheromones(self, num: int):
        if self.brood_pheromones < Location.PHEROMONE_COUNT_CAP and self.get_ground(
        ).is_passable():
            self.brood_pheromones += num

    def get_brood_pheromone_count(self):
        return self.brood_pheromones

    def get_pheromone_colour(self) -> Colour:
        return Colour(
            int(Colour.MAX_VALUE *
                (float(self.get_foraging_pheromone_count()) /
                 Location.PHEROMONE_COUNT_CAP)), 200, 200)
        # return Location.PHEROMONE_COLOUR.get_alt_blue(int(Colour.MAX_VALUE * (float(self.get_pheromone_count()) /
        #                                                                       Location.PHEROMONE_COUNT_CAP))).\
        #                         get_alt_red(int(Colour.MAX_VALUE * (float(self.get_foraging_pheromone_count()) /
        #                                                                       Location.PHEROMONE_COUNT_CAP)))

    def tick(self, world: World, elapsed: float,
             position: Position) -> (Actor, Location, Position):
        for obj in self.objects:
            obj.tick(world, elapsed, self, position)
        if self.get_ground().is_passable():
            # TODO: Fix this to be in terms of elapsed time (eg. with binomial distribution for probability).
            if random.random(
            ) < Location.PHEROMONE_DETERIORATION_CHANCE and self.pheromones > 0:
                self.pheromones -= 1
            if random.random(
            ) < Location.COMPLEX_PHEROMONE_DETERIORATION_CHANCE and self.foraging_pheromones > 0:
                self.foraging_pheromones -= 1
            if random.random(
            ) < Location.COMPLEX_PHEROMONE_DETERIORATION_CHANCE and self.brood_pheromones > 0:
                self.brood_pheromones -= 1
        return self.actor, self, position

    def get_objects_colour(self, world_state: WorldState) -> Colour:
        return Location.DEFAULT_OBJECTS_BASE_COLOUR.multiply((
            float(len(self.objects))/world_state.
                get_max_objects_in_location_count() if world_state.
                    get_max_objects_in_location_count() != 0 else 1) * Colour.MAX_VALUE).\
            get_alt_blue(Location.MIN_COLOUR_VAL)

    def get_colour(self, world_state: WorldState,
                   *object_kinds: [Kind]) -> Colour:
        if self.actor is not None:
            return self.actor.get_colour()
        elif len(object_kinds) == 0 and self.get_foraging_pheromone_count(
        ) > 0:  # get_pheromone_count
            return self.get_pheromone_colour()
        elif len(self.get_objects(*object_kinds)) > 0:
            return self.get_objects_colour(world_state)
        else:
            return self.ground.get_colour()

    def set_actor(self, actor: Actor):
        if self.get_actor() is not None:
            raise InvalidLocationException()
        self.actor = actor

    def add_object(self, object: Object):
        self.objects.append(object)

    def remove_object(self, obj: Object):
        """\
    Removes the specified Object from this Location.
        """
        self.objects.remove(obj)

    def get_object_by_kind(self, kind: Kind):
        """\
    Returns a list of objects in this location which have the
        specified kind.
        """
        return self.get_objects(kind)
        # ret_list: [Object] = []
        # for obj in self.objects:
        #     if kind in obj.get_kinds():
        #         ret_list.append(obj)
        # return ret_list

    def get_actor(self) -> Union[Actor, None]:
        """\
    Returns the actor at this location.
        """
        return self.actor

    def remove_actor(self) -> Actor:
        """\
    Removes the actor at this location, and returns it.
        """
        actor: Actor = self.actor
        self.actor = None
        return actor

    def get_ground(self) -> Ground:
        """\
    Returns the ground of this location.
        """
        return self.ground

    @staticmethod
    def __any_in(of, lst):
        for x in of:
            if x in lst:
                return True
        return False

    def get_objects(self, *with_kinds: [Kind]) -> [Object]:
        """\
    Returns the objects stored at this location.
        """
        if len(with_kinds) == 0:
            return self.objects
        else:
            return list(
                filter(lambda k: self.__any_in(k.get_kinds(), with_kinds),
                       self.objects))

    def is_free(self) -> bool:
        """\
    Return whether or not an actor can enter this location.
        """
        return self.ground.is_passable() and self.actor is None
예제 #12
0
 def get_colour(self) -> Colour:
     if self.age > 200:
         return Colour(255, 255, 0)
     else:
         return Colour(255, self.age, 255 - self.age)
예제 #13
0
class ModularAnt(Actor):
    COLOUR: Colour = Colour(150, 127, 0)
    AGE_COLOURING_PERIOD: float = 200
    AGE_SCALE: float = 0.0001 # ages are being input in days, and each tick represents some seconds, so ~1/10000 scale.
    ID: str = "mant"
    HOLD_POSITION_CHANCE: float = 0.2
    PHEROMONES_PER_TICK: int = 8
    FORAGING_PHEROMONES_PER_TICK: int = 3
    FORAGING_AGE: int = 130
    FORAGING_PHEROMONE_OVERRIDE_CHANCE: float = 0.3
    INITIAL_BIAS_HOLDNESS_WOBBLE: (float, float, float) = 0.2, 10, 0.3
    INTERACTION_RADIUS: int = 2
    INTERACTIONS_FILE_NAME: str = "interactions.txt"
    INTERACTING_TICKS_THRESHOLD: int = 3
    FOOD_DROP_CHANCE: float = 0.015
    ALT_PICKUP_CHANCE: float = 0.05

    # Indicates the largest ModularAnt id taken so far (-1 if no id has yet been taken).
    max_ant_id: int = -1

    # Interactions dictionary:
    interactions = {}


    @staticmethod
    def save_interactions():
        with open((World.WRITE_OUT_FILE_PATH / ModularAnt.INTERACTIONS_FILE_NAME), "w+") as file:
            file.write(str(ModularAnt.interactions))


    @staticmethod
    def load_interactions():
        with open((World.WRITE_OUT_FILE_PATH / ModularAnt.INTERACTIONS_FILE_NAME), "r") as file:
            ModularAnt.interactions = eval(file.read())


    @staticmethod
    def create(attributes: Attributes = None, kinds: [Kind] = []) -> Actor:
        return ModularAnt(attributes, kinds)


    @staticmethod
    def get_id() -> str:
        return ModularAnt.ID


    @staticmethod
    def can_interact(a1, a2) -> bool:
        """\
    :type a1: ModularAnt
    :type a2: ModularAnt
        """
        SIMILARITY_THRESHOLD: float = 1
        return (-1 * SIMILARITY_THRESHOLD <= Direction.dif_mag(a1.get_facing(),
            a2.get_facing().reversed()) <= SIMILARITY_THRESHOLD)


    @staticmethod
    def log_interaction(a1, a2):
        """\
    :type a1: ModularAnt
    :type a2: ModularAnt
        """
        id1: int = a1.get_ant_id()
        id2: int = a2.get_ant_id()
        ModularAnt.log_interaction_ids(id1, id2)

    @staticmethod
    def log_interaction_ids(id1: int, id2: int):
        entry: (int, int) = (min(id1, id2), max(id1, id2))
        if random.random() > 0.5:
            if entry in ModularAnt.interactions:
                ModularAnt.interactions[entry] += 1
            else:
                ModularAnt.interactions[entry] = 1


    def get_attributes_string(self) -> str:
        return repr([("age", self.age), ("bias", self.bias), ("holdness", self.holdness),
                ("wobble", self.wobble), ("hold_position_chance", self.hold_position_chance),
                ("carrying_food", self.carrying_food), ("interacting_with_id", self.interacting_with_id),
                     ("interacting_ticks", self.interacting_ticks)])


    def get_colour(self) -> Colour:
        return ModularAnt.COLOUR.get_alt_blue(int((2/math.pi) * Colour.MAX_VALUE * math.atan(
            self.age/ModularAnt.AGE_COLOURING_PERIOD)
        ))


    def move(self, frm: Location, to: Location):
        to.set_actor(self)
        frm.remove_actor()


    def interact(self, other_id: int):
        self.interacting_ticks = 0
        self.interacting_with_id = -1
        ModularAnt.log_interaction_ids(self.get_ant_id(), other_id)



    def tick(self, world: World, elapsed: float, location: Location, position: Position):
        self.age += elapsed * ModularAnt.AGE_SCALE
        self.current_wander_behaviour.set_age(self.age)
        self.current_wander_behaviour.set_seeking_food(not self.carrying_food)

        if random.random() > self.hold_position_chance * (1 + self.interacting_ticks):
            self.current_wander_behaviour.do(world, elapsed, location, position, self)

        if self.age < ModularAnt.FORAGING_AGE or random.random() < ModularAnt.\
                FORAGING_PHEROMONE_OVERRIDE_CHANCE:
            location.add_pheromones(ModularAnt.PHEROMONES_PER_TICK)
        else:
            if location.get_brood_pheromone_count() == 0 and location.get_pheromone_count() == 0:
                location.add_foraging_pheromones(ModularAnt.FORAGING_PHEROMONES_PER_TICK)

        # If this ant is in the foraging area, it will pick up food readily.
        if not self.carrying_food:
            if location.get_ground().get_id() == ForageGrounds.get_id() or \
                    random.random() < ModularAnt.ALT_PICKUP_CHANCE:
                foods: [Object] = location.get_objects(Kind.FOOD)
                if len(foods) > 0:
                    self.carrying_food = True
                    location.remove_object(foods[-1])
        elif location.get_ground().get_id() == Nest.get_id():
            if random.random() < ModularAnt.FOOD_DROP_CHANCE or sum(map(lambda l: len(l.get_objects(Kind.FOOD)),
                    world.get_adjacent_locations(position.x, position.y, 1, False))) > 0:
                location.add_object(Food())
                self.carrying_food = False


        # Measure interactions:
        others: [int] = []
        for loc in world.get_adjacent_locations(position.x, position.y, ModularAnt.INTERACTION_RADIUS, False):
            if loc.get_actor() is None:
                continue
            if isinstance(loc.get_actor(), ModularAnt):
                other: ModularAnt = loc.get_actor()
                if ModularAnt.can_interact(self, other):
                    others.append(other.get_ant_id())
        if self.interacting_with_id in others:
            self.interacting_ticks += 1
            if self.interacting_ticks > ModularAnt.INTERACTING_TICKS_THRESHOLD:
                self.interact(self.interacting_with_id)
        elif len(others) > 0:
            self.interacting_with_id = others[0]
            self.interacting_ticks = 0



    def get_facing(self) -> Direction:
        return self.current_wander_behaviour.facing


    def get_ant_id(self) -> int:
        return self.ant_id


    def __init__(self, attributes: Union[Attributes, None], kinds: [Kind]):
        if Kind.ANT not in kinds:
            kinds.append(Kind.ANT)
        super().__init__(kinds)

        # AI attributes.
        self.bias, self.holdness, self.wobble = ModularAnt.INITIAL_BIAS_HOLDNESS_WOBBLE
        self.hold_position_chance: float = ModularAnt.HOLD_POSITION_CHANCE
        self.age: float = 0
        self.carrying_food: bool = False
        self.interacting_with_id: int = -1
        self.interacting_ticks: int = 0
        if attributes is not None:
            # Overwrite (potentially all) attributes with input values.
            attributes.set_for(self)

        # ID-related attributes, forcibly controlled by the ant.
        self.ant_id = (ModularAnt.max_ant_id + 1)
        ModularAnt.max_ant_id += 1

        # Create a behaviour for it.
        self.current_wander_behaviour: WanderPheromoneBehaviour = WanderPheromoneBehaviour(self.bias, self.holdness, self.wobble)
예제 #14
0
 def get_colour(self) -> Colour:
     return Colour()