Do pathfinding from the hero to the mouse point

It works finally! And uses A*!
This commit is contained in:
Eryn Wells 2023-03-07 22:18:31 -08:00
parent a8bbc47668
commit 2d82d9834f
4 changed files with 42 additions and 35 deletions

View file

@ -28,7 +28,8 @@ class Interface:
self.map_window = MapWindow( self.map_window = MapWindow(
Rect.from_raw_values(0, 0, size.width, size.height - 5), Rect.from_raw_values(0, 0, size.width, size.height - 5),
engine.map) engine.map,
engine.hero)
self.info_window = InfoWindow( self.info_window = InfoWindow(
Rect.from_raw_values(0, size.height - 5, 28, 5)) Rect.from_raw_values(0, size.height - 5, 28, 5))
self.message_window = MessageLogWindow( self.message_window = MessageLogWindow(
@ -43,7 +44,7 @@ class Interface:
hero = self.engine.hero hero = self.engine.hero
self.info_window.update_hero(hero) self.info_window.update_hero(hero)
self.map_window.update_drawable_map_bounds(hero) self.map_window.update_drawable_map_bounds()
sorted_entities = sorted(self.engine.entities, key=lambda e: e.render_order.value) sorted_entities = sorted(self.engine.entities, key=lambda e: e.render_order.value)
self.map_window.entities = sorted_entities self.map_window.entities = sorted_entities
@ -64,6 +65,7 @@ class Interface:
context.present(self.console) context.present(self.console)
for event in tev.wait(): for event in tev.wait():
context.convert_event(event)
did_handle = self.event_handler.dispatch(event) did_handle = self.event_handler.dispatch(event)
if did_handle: if did_handle:
continue continue

View file

@ -1,23 +1,25 @@
# Eryn Wells <eryn@erynwells.me> # Eryn Wells <eryn@erynwells.me>
from typing import Optional from typing import Generic, Optional, TypeVar
from tcod import event as tev from tcod import event as tev
from tcod.console import Console from tcod.console import Console
from ...geometry import Point, Rect, Vector from ...geometry import Point, Rect, Vector
WindowT = TypeVar('WindowT', bound='Window')
class Window: class Window:
'''A user interface window. It can be framed and it can handle events.''' '''A user interface window. It can be framed and it can handle events.'''
class EventHandler(tev.EventDispatch[bool]): class EventHandler(tev.EventDispatch[bool], Generic[WindowT]):
''' '''
Handles events for a Window. Event dispatch methods return True if the event Handles events for a Window. Event dispatch methods return True if the event
was handled and no further action is needed. was handled and no further action is needed.
''' '''
def __init__(self, window: 'Window'): def __init__(self, window: WindowT):
super().__init__() super().__init__()
self.window = window self.window = window

View file

@ -1,6 +1,6 @@
# Eryn Wells <eryn@erynwells.me> # Eryn Wells <eryn@erynwells.me>
from typing import List from typing import List, Optional
import numpy as np import numpy as np
import tcod.event as tev import tcod.event as tev
@ -16,7 +16,7 @@ from ...object import Entity, Hero
class MapWindow(Window): class MapWindow(Window):
'''A Window that displays a game map''' '''A Window that displays a game map'''
class EventHandler(Window.EventHandler): class EventHandler(Window.EventHandler['MapWindow']):
'''An event handler for the MapWindow.''' '''An event handler for the MapWindow.'''
def ev_mousemotion(self, event: tev.MouseMotion) -> bool: def ev_mousemotion(self, event: tev.MouseMotion) -> bool:
@ -24,23 +24,40 @@ class MapWindow(Window):
if not mouse_point: if not mouse_point:
return False return False
# TODO: Convert window point to map point log.UI.info('Mouse point in window %s', mouse_point)
# TODO: Perform a path finding operation from the hero to the mouse point
# TODO: Highlight those points on the map hero = self.window.hero
if not hero:
return False
map_point = self.window.convert_window_point_to_map(mouse_point)
log.UI.info('Mouse point in map %s', map_point)
map_ = self.window.map
path = map_.find_walkable_path_from_point_to_point(hero.position, map_point)
map_.highlight_points(path)
return False return False
# pylint: disable=redefined-builtin # pylint: disable=redefined-builtin
def __init__(self, bounds: Rect, map: Map, **kwargs): def __init__(self, bounds: Rect, map: Map, hero: Hero, **kwargs):
super().__init__(bounds, **kwargs) super().__init__(bounds, event_handler=self.__class__.EventHandler(self), **kwargs)
self.map = map self.map = map
self.drawable_map_bounds = map.bounds self.drawable_map_bounds = map.bounds
self.hero = hero
self.entities: List[Entity] = [] self.entities: List[Entity] = []
self._draw_bounds = self.drawable_bounds self._draw_bounds = self.drawable_bounds
def update_drawable_map_bounds(self, hero: Hero): def convert_window_point_to_map(self, point: Point) -> Point:
'''
Convert a point in window coordinates to a point relative to the map's
origin point.
'''
return point - Vector.from_point(self._draw_bounds.origin)
def update_drawable_map_bounds(self):
''' '''
Figure out what portion of the map is drawable and update Figure out what portion of the map is drawable and update
`self.drawable_map_bounds`. This method attempts to keep the hero `self.drawable_map_bounds`. This method attempts to keep the hero
@ -59,7 +76,7 @@ class MapWindow(Window):
return return
# Attempt to keep the player centered in the viewport. # Attempt to keep the player centered in the viewport.
hero_point = hero.position hero_point = self.hero.position
if viewport_is_wider_than_map: if viewport_is_wider_than_map:
x = 0 x = 0
@ -115,8 +132,6 @@ class MapWindow(Window):
drawable_map_bounds = self.drawable_map_bounds drawable_map_bounds = self.drawable_map_bounds
drawable_bounds = self.drawable_bounds drawable_bounds = self.drawable_bounds
log.UI.info('Drawing map')
map_slice = np.s_[ map_slice = np.s_[
drawable_map_bounds.min_x: drawable_map_bounds.max_x + 1, drawable_map_bounds.min_x: drawable_map_bounds.max_x + 1,
drawable_map_bounds.min_y: drawable_map_bounds.max_y + 1] drawable_map_bounds.min_y: drawable_map_bounds.max_y + 1]
@ -126,19 +141,12 @@ class MapWindow(Window):
console_draw_bounds.min_x: console_draw_bounds.max_x + 1, console_draw_bounds.min_x: console_draw_bounds.max_x + 1,
console_draw_bounds.min_y: console_draw_bounds.max_y + 1] console_draw_bounds.min_y: console_draw_bounds.max_y + 1]
log.UI.debug('Map bounds=%s, slice=%s', drawable_map_bounds, map_slice)
log.UI.debug('Console bounds=%s, slice=%s', drawable_bounds, console_slice)
console.tiles_rgb[console_slice] = self.map.composited_tiles[map_slice] console.tiles_rgb[console_slice] = self.map.composited_tiles[map_slice]
log.UI.info('Done drawing map')
def _draw_entities(self, console): def _draw_entities(self, console):
map_bounds_vector = Vector.from_point(self.drawable_map_bounds.origin) map_bounds_vector = Vector.from_point(self.drawable_map_bounds.origin)
draw_bounds_vector = Vector.from_point(self._draw_bounds.origin) draw_bounds_vector = Vector.from_point(self._draw_bounds.origin)
log.UI.info('Drawing entities')
for ent in self.entities: for ent in self.entities:
# Only draw entities that are in the field of view # Only draw entities that are in the field of view
if not self.map.point_is_visible(ent.position): if not self.map.point_is_visible(ent.position):
@ -161,5 +169,3 @@ class MapWindow(Window):
string=ent.symbol, string=ent.symbol,
fg=ent.foreground, fg=ent.foreground,
bg=tuple(map_tile_at_entity_position['bg'][:3])) bg=tuple(map_tile_at_entity_position['bg'][:3]))
log.UI.info('Done drawing entities')

View file

@ -111,16 +111,13 @@ class Map:
for pt in points: for pt in points:
self.highlighted[pt.x, pt.y] = True self.highlighted[pt.x, pt.y] = True
def print_to_console(self, console: tcod.Console, bounds: Rect) -> None: def find_walkable_path_from_point_to_point(self, point_a: Point, point_b: Point) -> Iterable[Point]:
'''Render the map to the console.''' '''
size = self.size Find a path between point A and point B using tcod's A* implementation.
'''
# If a tile is in the visible array, draw it with the "light" color. If it's not, but it's in the explored a_star = tcod.path.AStar(self.tiles['walkable'])
# array, draw it with the "dark" color. Otherwise, draw it as Empty. path = a_star.get_path(point_a.x, point_a.y, point_b.x, point_b.y)
console.tiles_rgb[0:size.width, 0:size.height] = np.select( return map(lambda t: Point(t[0], t[1]), path)
condlist=[self.highlighted, self.visible, self.explored],
choicelist=[self.tiles['highlighted'], self.tiles['light'], self.tiles['dark']],
default=Shroud)
def __str__(self): def __str__(self):
string = '' string = ''