playscii/games/crawler/scripts/topview.py
Jared Miller 4f8b009a71
Reorganize source into playscii/ package
Move all root .py files into playscii/ package directory.
Rename playscii.py to app.py, add __main__.py entry point.
Convert bare imports to relative (within package) and absolute
(in formats/ and games/). Data dirs stay at root.
2026-02-13 09:19:59 -05:00

134 lines
5.7 KiB
Python

from games.crawler.scripts.crawler import (
DIR_EAST,
DIR_NORTH,
DIR_SOUTH,
DIR_WEST,
)
from playscii.art import TileIter
from playscii.game_object import GameObject
from playscii.vector import get_tiles_along_integer_line
class CrawlTopDownView(GameObject):
art_src = "maze2"
art_off_pct_x, art_off_pct_y = 0, 0
# we will be modifying this view at runtime so don't write on the source art
use_art_instance = True
# first character we find with this index will be where we spawn player
playerstart_char_index = 147
# undiscovered = player has never seen this tile
undiscovered_color_index = 1 # black
# discovered = player has seen this tile but isn't currently looking at it
discovered_color_index = 12 # dark grey
def pre_first_update(self):
# scan art for spot to spawn player
player_x, player_y = -1, -1
for frame, layer, x, y in TileIter(self.art):
if (
self.art.get_char_index_at(frame, layer, x, y)
== self.playerstart_char_index
):
player_x, player_y = self.x + x, self.y - y
# clear the tile at this spot in our art
self.art.set_char_index_at(frame, layer, x, y, 0)
break
self.world.player = self.world.spawn_object_of_class(
"CrawlPlayer", player_x, player_y
)
# give player a ref to us
self.world.player.maze = self
# make a copy of original layer to color for visibility, hide original
self.art.duplicate_layer(0)
self.art.layers_visibility[0] = False
for _frame, layer, x, y in TileIter(self.art):
if layer == 0:
continue
# set all tiles undiscovered
self.art.set_color_at(0, layer, x, y, self.undiscovered_color_index)
self.art.mark_all_frames_changed() # DEBUG - this fixes the difference in result when use_art_instance=True! why?
# keep a list of tiles player can see
self.player_visible_tiles = []
def is_tile_solid(self, x, y):
return self.art.get_char_index_at(0, 0, x, y) != 0
# world to tile: self.get_tile_at_point(world_x, world_y)
# tile to world: self.get_tile_loc(tile_x, tile_y)
def get_visible_tiles(self, x, y, dir_x, dir_y, tile_range, see_thru_walls=False):
"return tiles visible from given point facing given direction"
# NOTE: all the calculations here are done in this object's art's tile
# coordinates, not world space. so -Y is up/north, -X is left/west.
tiles = []
# find back left corner of frustum and direction to scan along
if (dir_x, dir_y) == DIR_NORTH:
scan_start_x, scan_start_y = x - tile_range, y - tile_range
scan_dir_x, scan_dir_y = 1, 0
elif (dir_x, dir_y) == DIR_SOUTH:
scan_start_x, scan_start_y = x + tile_range, y + tile_range
scan_dir_x, scan_dir_y = -1, 0
elif (dir_x, dir_y) == DIR_EAST:
scan_start_x, scan_start_y = x + tile_range, y - tile_range
scan_dir_x, scan_dir_y = 0, 1
elif (dir_x, dir_y) == DIR_WEST:
scan_start_x, scan_start_y = x - tile_range, y + tile_range
scan_dir_x, scan_dir_y = 0, -1
# scan back of frustum tile by tile left to right,
# checking each tile hit
scan_distance = 0
scan_length = tile_range * 2 + 1 # TODO make sure this is correct
while scan_distance < scan_length:
scan_x = scan_start_x + (scan_dir_x * scan_distance)
scan_y = scan_start_y + (scan_dir_y * scan_distance)
hit_tiles = get_tiles_along_integer_line(x, y, scan_x, scan_y)
for tile in hit_tiles:
tile_x, tile_y = tile[0], tile[1]
# skip out-of-bounds tiles
if (
tile_x < 0
or tile_x >= self.art.width
or tile_y < 0
or tile_y >= self.art.height
):
continue
# whether this tile is solid or not, we have seen it
if tile not in tiles:
tiles.append((tile_x, tile_y))
if not see_thru_walls and self.is_tile_solid(*tile):
break
scan_distance += 1
return tiles
def update_tile_visibilities(self):
"""
update our art's tile visuals based on what tiles can be, can't be,
or have been seen.
"""
previously_visible_tiles = self.player_visible_tiles[:]
p = self.world.player
px, py = self.get_tile_at_point(p.x, p.y)
self.player_visible_tiles = self.get_visible_tiles(
px, py, *p.direction, p.view_range_tiles, see_thru_walls=False
)
# print(self.player_visible_tiles)
# color currently visible tiles
for tile in self.player_visible_tiles:
# print(tile)
if (
tile[0] < 0
or tile[0] >= self.art.width
or tile[1] < 0
or tile[1] >= self.art.height
):
continue
if self.is_tile_solid(*tile):
orig_color = self.art.get_fg_color_index_at(0, 0, *tile)
self.art.set_color_at(0, 1, *tile, orig_color)
else:
# self.art.set_color_at(0, 1, *tile, randint(2, 14)) # DEBUG
pass
# color "previously seen" tiles
for tile in previously_visible_tiles:
if tile not in self.player_visible_tiles and self.is_tile_solid(*tile):
self.art.set_color_at(0, 1, *tile, self.discovered_color_index)