Implement a basic AI for HostileMonster
This AI will walk randomly around the dungeon (pausing periodically) and if the Hero comes into view, will b-line and attack
This commit is contained in:
parent
1f750a0c7c
commit
b604ff30ec
1 changed files with 95 additions and 5 deletions
100
roguebasin/ai.py
100
roguebasin/ai.py
|
@ -1,22 +1,112 @@
|
|||
# Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
import logging
|
||||
from os import path
|
||||
import random
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from .actions import Action, WaitAction
|
||||
import numpy as np
|
||||
import tcod
|
||||
|
||||
from .actions import Action, BumpAction, MeleeAction, WaitAction
|
||||
from .components import Component
|
||||
from .geometry import Direction, Point
|
||||
from .object import Entity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .engine import Engine
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
class AI(Component):
|
||||
def __init__(self, entity: Entity) -> None:
|
||||
super().__init__()
|
||||
self.entity = entity
|
||||
|
||||
def act(self, engine: 'Engine') -> Action:
|
||||
def act(self, engine: 'Engine') -> Optional[Action]:
|
||||
'''Produce an action to perform'''
|
||||
raise NotImplementedError()
|
||||
|
||||
class HostileEnemy(AI):
|
||||
def act(self, engine: 'Engine') -> Action:
|
||||
return WaitAction(self.entity)
|
||||
def act(self, engine: 'Engine') -> Optional[Action]:
|
||||
visible_tiles = tcod.map.compute_fov(
|
||||
engine.map.tiles['transparent'],
|
||||
pov=tuple(self.entity.position),
|
||||
radius=self.entity.sight_radius)
|
||||
|
||||
hero_position = engine.hero.position
|
||||
hero_is_visible = visible_tiles[hero_position.x, hero_position.y]
|
||||
|
||||
if hero_is_visible:
|
||||
path_to_hero = self.get_path_to(hero_position, engine)
|
||||
entity_position = self.entity.position
|
||||
|
||||
next_position = path_to_hero.pop(0) if len(path_to_hero) else hero_position
|
||||
direction_to_next_position = entity_position.direction_to_adjacent_point(next_position)
|
||||
LOG.info('Hero is visible to %s, bumping %s (%s)', self.entity, direction_to_next_position, next_position)
|
||||
|
||||
return BumpAction(self.entity, direction_to_next_position)
|
||||
else:
|
||||
move_or_wait_chance = random.random()
|
||||
if move_or_wait_chance <= 0.7:
|
||||
# Pick a random adjacent tile to move to
|
||||
directions = list(Direction.all())
|
||||
while len(directions) > 0:
|
||||
direction = random.choice(directions)
|
||||
directions.remove(direction)
|
||||
new_position = self.entity.position + direction
|
||||
overlaps_existing_entity = any(new_position == ent.position for ent in engine.entities)
|
||||
tile_is_walkable = engine.map.tile_is_walkable(new_position)
|
||||
if not overlaps_existing_entity and tile_is_walkable:
|
||||
LOG.info('Hero is NOT visible to %s, bumping %s randomly', self.entity, direction)
|
||||
action = BumpAction(self.entity, direction)
|
||||
break
|
||||
else:
|
||||
# If this entity somehow can't move anywhere, just wait
|
||||
LOG.info("Hero is NOT visible to %s and it can't move anywhere, waiting", self.entity)
|
||||
action = WaitAction(self.entity)
|
||||
|
||||
return action
|
||||
else:
|
||||
return WaitAction(self.entity)
|
||||
|
||||
def get_path_to(self, point: Point, engine: 'Engine') -> List[Point]:
|
||||
'''Compute a path to the given position.
|
||||
|
||||
Copied from the Roguelike tutorial. :)
|
||||
|
||||
Arguments
|
||||
---------
|
||||
point : Point
|
||||
The target point
|
||||
engine : Engine
|
||||
The game engine
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[Point]
|
||||
An array of Points representing a path from the Entity's position to the target point
|
||||
'''
|
||||
# Copy the walkable array
|
||||
cost = np.array(engine.map.tiles['walkable'], dtype=np.int8)
|
||||
|
||||
for ent in engine.entities:
|
||||
# Check that an entity blocks movement and the cost isn't zero (blocking)
|
||||
position = ent.position
|
||||
if ent.blocks_movement and cost[position.x, position.y]:
|
||||
# Add to the cost of a blocked position. A lower number means more enemies will crowd behind each other
|
||||
# in hallways. A higher number means enemies will take longer paths in order to surround the player.
|
||||
cost[position.x, position.y] += 10
|
||||
|
||||
# Create a graph from the cost array and pass that graph to a new pathfinder.
|
||||
graph = tcod.path.SimpleGraph(cost=cost, cardinal=2, diagonal=3)
|
||||
pathfinder = tcod.path.Pathfinder(graph)
|
||||
|
||||
# Set the starting position
|
||||
pathfinder.add_root(tuple(self.entity.position))
|
||||
|
||||
# Compute the path to the destination and remove the starting point.
|
||||
path: List[List[int]] = pathfinder.path_to(tuple(point))[1:].tolist()
|
||||
|
||||
# Convert from List[List[int]] to List[Tuple[int, int]].
|
||||
return [Point(index[0], index[1]) for index in path]
|
Loading…
Add table
Add a link
Reference in a new issue