Skip to content

Commit

Permalink
updates for new ai components
Browse files Browse the repository at this point in the history
  • Loading branch information
ShiJbey committed Nov 15, 2022
1 parent 05242ad commit 06fdc96
Show file tree
Hide file tree
Showing 13 changed files with 327 additions and 45 deletions.
13 changes: 8 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ incrementing to a completely new version number.

## [0.9.4]

**0.9.4 is not compatible with 0.9.3**

### Added

- `Building` class to identify when a business currently exists within the town vs.
when it is archived within the ECS for story sifting.
- Systems to update business components when they are pending opening, open for business, and closed for business and awaiting demolition.
when it is archived within the ECS for story sifting.
- Systems to update business components when they are pending opening, open for business, and closed for business and
awaiting demolition.
- New status components to identify Businesses at different phases in their lifecycle:
`ClosedForBusiness`, `OpenForBusiness`, `PendingOpening`
`ClosedForBusiness`, `OpenForBusiness`, `PendingOpening`
- New PyGame UI elements for displaying information about a GameObject
- Strings may be used as world seeds
- `CHANGELOG.md` file
Expand All @@ -32,9 +35,9 @@ when it is archived within the ECS for story sifting.
- Jupyter notebook and pygame samples
- samples category from dependencies within `setup.cfg`
- `events`, `town`, `land grid`, and `relationships` fields from `NeighborlyJsonExporter`.
These are duplicated when serializing the resources.
These are duplicated when serializing the resources.
- `SimulationBuilder.add_system()` and `SimulationBuilder.add_resource()`. To add
these, users need to encapsulate their content within a plugin
these, users need to encapsulate their content within a plugin
- Flake8 configuration from `setup.cfg`

### Fixed
Expand Down
25 changes: 9 additions & 16 deletions samples/demon_slayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,23 +638,17 @@ def execute(world: World, event: Event):
opponent = world.get_gameobject(event["Opponent"]).get_component(Demon)
rng = world.get_resource(NeighborlyEngine).rng
_death_event_type = LifeEvents.get("Death")
generated_events = [event]

slayer_success_chance = probability_of_winning(
opponent.power_level, challenger.power_level
)

demon_success_chance = probability_of_winning(
challenger.power_level, opponent.power_level
)
opponent_success_chance = probability_of_winning(opponent.power_level, challenger.power_level)
challenger_success_chance = probability_of_winning(challenger.power_level, opponent.power_level)

if rng.random() < slayer_success_chance:
if rng.random() < opponent_success_chance:
# Demon slayer wins
new_slayer_pl, _ = update_power_level(
opponent.power_level,
challenger.power_level,
slayer_success_chance,
demon_success_chance,
opponent_success_chance,
challenger_success_chance,
)

opponent.power_level = new_slayer_pl
Expand All @@ -665,15 +659,14 @@ def execute(world: World, event: Event):

if death_event:
_death_event_type.execute(world, death_event)
generated_events.append(death_event)

# Update Power Ranking
else:
# Demon wins
_, new_demon_pl = update_power_level(
challenger.power_level,
opponent.power_level,
demon_success_chance,
slayer_success_chance,
challenger_success_chance,
opponent_success_chance,
)

challenger.power_level = new_demon_pl
Expand All @@ -684,7 +677,7 @@ def execute(world: World, event: Event):

if death_event:
_death_event_type.execute(world, death_event)
generated_events.append(death_event)
# Update Power Ranking

return LifeEvent(
"ChallengeForPower",
Expand Down
64 changes: 64 additions & 0 deletions src/neighborly/builtin/ai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
Default implementations of AI modules
"""
from typing import Optional, List

from neighborly import World, GameObject, SimDateTime, NeighborlyEngine
from neighborly.builtin.components import LocationAliases, OpenToPublic, CurrentLocation
from neighborly.core.action import Action, AvailableActions
from neighborly.core.location import Location
from neighborly.core.routine import Routine


class DefaultMovementModule:

def get_next_location(self, world: World, gameobject: GameObject) -> Optional[int]:
date = world.get_resource(SimDateTime)
routine = gameobject.try_component(Routine)
location_aliases = gameobject.try_component(LocationAliases)

if routine:
routine_entry = routine.get_entry(date.weekday, date.hour)

if (
routine_entry
and isinstance(routine_entry.location, str)
and location_aliases
):
return location_aliases.aliases[routine_entry.location]

elif routine_entry:
return int(routine_entry.location)

potential_locations: List[int] = list(
map(
lambda res: res[0],
world.get_components(Location, OpenToPublic),
)
)

if potential_locations:
return world.get_resource(NeighborlyEngine).rng.choice(potential_locations)

return None


class DefaultSocialAIModule:

def get_next_action(self, world: World, gameobject: GameObject) -> Optional[Action]:
current_location_comp = gameobject.try_component(CurrentLocation)

if current_location_comp is None:
return None

current_location = world.get_gameobject(current_location_comp.location)

available_actions = current_location.try_component(AvailableActions)

if available_actions is None:
return None

for action in available_actions.actions:
...

return None
6 changes: 3 additions & 3 deletions src/neighborly/builtin/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def close_for_business(business: Business) -> None:
],
)

world.get_resource(EventLog).record_event(close_for_business_event)
world.get_resource(EventLog).record_event(world, close_for_business_event)

for employee in business.get_employees():
layoff_employee(business, world.get_gameobject(employee))
Expand All @@ -288,7 +288,7 @@ def leave_job(world: World, employee: GameObject) -> None:
],
)

world.get_resource(EventLog).record_event(fired_event)
world.get_resource(EventLog).record_event(world, fired_event)

business.get_component(Business).remove_employee(employee.id)

Expand Down Expand Up @@ -323,7 +323,7 @@ def layoff_employee(business: Business, employee: GameObject) -> None:
],
)

world.get_resource(EventLog).record_event(fired_event)
world.get_resource(EventLog).record_event(world, fired_event)

if not employee.has_component(WorkHistory):
employee.add_component(WorkHistory())
Expand Down
21 changes: 14 additions & 7 deletions src/neighborly/builtin/systems.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ def process(self, *args, **kwargs) -> None:
)

event_log.record_event(
self.world,
Event(
name="BecameBusinessOwner",
timestamp=date.to_iso_str(),
Expand Down Expand Up @@ -362,6 +363,7 @@ def process(self, *args, **kwargs) -> None:
)

event_log.record_event(
self.world,
Event(
"HiredAtBusiness",
date.to_iso_str(),
Expand Down Expand Up @@ -489,10 +491,10 @@ def choose_random_eligible_business(
archetype.get_instances() < archetype.get_max_instances()
and town.population >= archetype.get_min_population()
and (
archetype.get_year_available()
<= date.year
< archetype.get_year_obsolete()
)
archetype.get_year_available()
<= date.year
< archetype.get_year_obsolete()
)
):
archetype_choices.append(archetype)
archetype_weights.append(archetype.get_spawn_frequency())
Expand Down Expand Up @@ -698,6 +700,7 @@ def run(self, *args, **kwargs) -> None:

# Record a life event
event_logger.record_event(
self.world,
Event(
name="MoveIntoTown",
timestamp=date.to_iso_str(),
Expand Down Expand Up @@ -764,6 +767,7 @@ def run(self, *args, **kwargs) -> None:
character.gameobject.add_component(Teen())
character.gameobject.remove_component(Child)
event_log.record_event(
self.world,
Event(
name="BecomeTeen",
timestamp=current_date.to_iso_str(),
Expand All @@ -786,6 +790,7 @@ def run(self, *args, **kwargs) -> None:
character.gameobject.add_component(Unemployed())

event_log.record_event(
self.world,
Event(
name="BecomeYoungAdult",
timestamp=current_date.to_iso_str(),
Expand All @@ -801,6 +806,7 @@ def run(self, *args, **kwargs) -> None:
):
character.gameobject.remove_component(YoungAdult)
event_log.record_event(
self.world,
Event(
name="BecomeAdult",
timestamp=current_date.to_iso_str(),
Expand All @@ -817,6 +823,7 @@ def run(self, *args, **kwargs) -> None:
):
character.gameobject.add_component(Elder())
event_log.record_event(
self.world,
Event(
name="BecomeElder",
timestamp=current_date.to_iso_str(),
Expand Down Expand Up @@ -863,15 +870,14 @@ def process(self, *args, **kwargs):
town.increment_population()

baby.get_component(Age).value = (
current_date - pregnancy.due_date
).hours / HOURS_PER_YEAR
current_date - pregnancy.due_date
).hours / HOURS_PER_YEAR

baby.get_component(CharacterName).surname = birthing_parent_name.surname

move_to_location(self.world, birthing_parent, "home")

if birthing_parent.has_component(CurrentLocation):

current_location = birthing_parent.get_component(CurrentLocation)

move_to_location(
Expand Down Expand Up @@ -943,6 +949,7 @@ def process(self, *args, **kwargs):
# Pregnancy event dates are retconned to be the actual date that the
# child was due.
event_logger.record_event(
self.world,
Event(
name="ChildBirth",
timestamp=pregnancy.due_date.to_iso_str(),
Expand Down
53 changes: 53 additions & 0 deletions src/neighborly/core/action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from __future__ import annotations

from typing import List, Set, Union, Tuple, Callable

from neighborly.core.ecs import Component, World
from neighborly.core.event import EventProbabilityFn, Event


class AvailableActions(Component):
"""
Tracks all the Actions that are available at a given location
Attributes
----------
actions: Set[Action]
The actions that characters can take
"""

__slots__ = "actions"

def __init__(self, actions: List[Action]) -> None:
super().__init__()
self.actions: Set[Action] = set(actions)


class Action:

def __init__(self, uid: int, rules: List[ActionRule], *roles: str) -> None:
self.uid: int = uid
self.rules: List[ActionRule] = rules

def find_match(self, world: World) -> Tuple[Event, float]:
raise NotImplementedError()

def __eq__(self, other: Action) -> bool:
return self.uid == other.uid

def __hash__(self) -> int:
return self.uid


class ActionRule:
"""
ActionRules are combinations of patterns and probabilities for
when an action is allowed to occur. A single action is mapped
to one or more action rules.
"""

def __init__(self, bind_fn: Callable[..., Event], probability: Union[EventProbabilityFn, float] = 1.0) -> None:
self.bind_fn: Callable[..., Event] = bind_fn
self.probability_fn: EventProbabilityFn = (
probability if callable(probability) else (lambda world, event: probability)
)
Loading

0 comments on commit 06fdc96

Please sign in to comment.