Compare commits

3 Commits

Author SHA1 Message Date
d05eec6c03 Part 2. 2021-05-26 16:36:59 -04:00
3fff7ef498 Part 2 in progress. 2021-05-26 13:31:34 -04:00
ca0f09cc07 Part 1 of the tutorial. 2021-05-26 13:06:40 -04:00
7 changed files with 201 additions and 157 deletions

42
actions.py Normal file
View File

@@ -0,0 +1,42 @@
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from engine import Engine
from entity import Entity
class Action:
def perform(self, engine: Engine, entity: Entity) -> None:
"""Perform this action with the objects needed to determine its scope
'engine' is the scope this action is being perfrmed in.
'entity is the object performing the action.
This method must be overridden by Action subclasses
"""
raise NotImplementedError()
class EscapeAction(Action):
def perform(self, engine: Engine, entity: Entity) -> None:
raise SystemExit()
class MovementAction(Action):
def __init__(self, dx: int, dy: int):
super().__init__()
self.dx = dx
self.dy = dy
def perform(self, engine: Engine, entity: Entity) -> None:
dest_x = entity.x + self.dx
dest_y = entity.y + self.dy
if not engine.game_map.in_bounds(dest_x, dest_y):
return # Destination is out of bounds.
if not engine.game_map.tiles["walkable"][dest_x, dest_y]:
return # Destination is blocked by a tile
entity.move(self.dx, self.dy)

View File

@@ -1,65 +1,36 @@
import tcod as tc from typing import Set, Iterable, Any
from tcod.context import Context
from tcod.console import Console
from actions import EscapeAction, MovementAction
from entity import Entity from entity import Entity
from render_functions import * from game_map import GameMap
from game_map import * from input_handlers import EventHandler
from input_handlers import handle_keys
def main(): class Engine:
screen_width = 80 def __init__(self, entities: Set[Entity], event_handler: EventHandler, game_map: GameMap, player: Entity):
screen_height = 50 self.entities = entities
self.event_handler = event_handler
self.game_map = game_map
self.player = player
map_width = 80 def handle_events(self, events: Iterable[Any]) -> None:
map_height = 50 for event in events:
action = self.event_handler.dispatch(event)
room_max_size = 10 if action is None:
room_min_size = 6 continue
max_rooms = 30
colors = { action.perform(self, self.player)
'dark_wall': tc.Color(0, 0, 100),
'dark_ground': tc.Color(50, 50, 150)
}
player = Entity(int(screen_width/2), int(screen_height/2), '@', tc.white) def render(self, console: Console, context: Context) -> None:
npc = Entity(int(screen_width/2), int(screen_height/2), '@', tc.yellow) self.game_map.render(console)
entities = [npc, player]
tc.console_set_custom_font('arial10x10.png', tc.FONT_TYPE_GREYSCALE | tc.FONT_LAYOUT_TCOD) for entity in self.entities:
console.print(entity.x, entity.y, entity.char, fg=entity.color)
tc.console_init_root(screen_width, screen_height, 'tutorial revised', False) context.present(console)
con = tc.console_new(screen_width, screen_height)
game_map = GameMap(map_width, map_height) console.clear()
game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player)
key = tc.Key()
mouse = tc.Mouse()
while not tc.console_is_window_closed():
tc.sys_check_for_event(tc.EVENT_KEY_PRESS, key, mouse)
render_all(con, entities, game_map, screen_width, screen_height, colors)
tc.console_flush()
clear_all(con, entities)
action = handle_keys(key)
move = action.get('move')
exit = action.get('exit')
fullscreen = action.get('fullscreen')
if move:
dx, dy = move
if not game_map.is_blocked(player.x + dx, player.y + dy):
player.move(dx, dy)
if exit:
return True
if fullscreen:
tc.console_set_fullscreen(not tc.console_is_fullscreen())
if __name__ == '__main__':
main()

View File

@@ -1,14 +1,16 @@
from typing import Tuple
class Entity: class Entity:
""" """
Generic object to represent players, enemies, items, etc. Generic object to represent players, enemies, items, etc.
""" """
def __init__(self, x, y, char, color): def __init__(self, x: int, y: int, char: str, color: tuple[int, int, int]):
self.x = x self.x = x
self.y = y self.y = y
self.char = char self.char = char
self.color = color self.color = color
def move(self, dx, dy): def move(self, dx: int, dy: int) -> None:
self.x += dx self.x += dx
self.y += dy self.y += dy

View File

@@ -1,90 +1,18 @@
from map_objects import * import numpy as np
from random import randint from tcod.console import Console
import tile_types
class GameMap: class GameMap:
def __init__(self, width, height): def __init__(self, width: int, height: int):
self.width = width self.width, self.height = width, height
self.height = height self.tiles = np.full((width,height), fill_value=tile_types.floor, order="F")
self.tiles = self.initialize_tiles()
def initialize_tiles(self): self.tiles[30:33, 22] = tile_types.wall
tiles = [[Tile(True) for y in range(self.height)] for x in range(self.width)]
return tiles def in_bounds(self, x: int, y: int) -> bool:
"""Return True if x and y are inside the bounds of this map"""
return 0 <= x < self.width and 0 <= y < self.height
def is_blocked(self, x, y): def render(self, console: Console) -> None:
if self.tiles[x][y].blocked: console.tiles_rgb[0:self.width, 0:self.height] = self.tiles["dark"]
return True
return False
def create_room(self, room):
# go through the tiles in the rectangle and make them passable
for x in range(room.x1 + 1, room.x2):
for y in range(room.y1 +1, room.y2):
self.tiles[x][y].blocked = False
self.tiles[x][y].block_sight = False
def create_h_tunnel(self, x1, x2, y):
for x in range(min(x1, x2), max(x1, x2) + 1):
self.tiles[x][y].blocked = False
self.tiles[x][y].block_sight = False
def create_v_tunnel(self, y1, y2, x):
for y in range(min(y1, y2), max(y1, y2) + 1):
self.tiles[x][y].blocked = False
self.tiles[x][y].block_sight = False
def make_map(self, max_rooms, room_min_size, room_max_size, map_width, map_height, player):
rooms = []
num_rooms = 0
for r in range(max_rooms):
# print(rooms)
# print(num_rooms)
# random width and height
w = randint(room_min_size, room_max_size)
h = randint(room_min_size, room_max_size)
# random position without going out of bounds
x = randint(0, map_width - w - 1)
y = randint(0, map_height - h - 1)
#call Rect Class
new_room = Rect(x, y, w, h)
# if rooms == []:
# print('first')
# rooms.append(new_room)
#run through other rooms to check for overlap
for other_room in rooms:
if new_room.intersect(other_room):
break
else:
# no overlap, room is valid
# paint to map's tiles
self.create_room(new_room)
#center coords of new room
(new_x, new_y) = new_room.center()
if num_rooms == 0:
#first room where player starts
player.x = new_x
player.y = new_y
else:
# all rooms after the first are connected
# center coords of previous room
(prev_x, prev_y) = rooms[num_rooms - 1].center()
if randint(0, 1) == 1:
#first horizontally then vertically
self.create_h_tunnel(prev_x, new_x, prev_y)
self.create_v_tunnel(prev_y, new_y, new_x)
else:
#first vertically then horizontally
self.create_v_tunnel(prev_y, new_y, prev_x)
self.create_h_tunnel(prev_x, new_x, new_y)
rooms.append(new_room)
num_rooms += 1

View File

@@ -1,19 +1,26 @@
import tcod as tc from typing import Optional
import tcod.event
from actions import Action, EscapeAction, MovementAction
class EventHandler(tcod.event.EventDispatch[Action]):
def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]:
raise SystemExit()
def handle_keys(key): def ev_keydown(self, event: "tcod.event.KeyDown") -> Optional[Action]:
if key.vk == tc.KEY_UP: action: Optional[Action] = None
return {'move': (0,-1)}
elif key.vk == tc.KEY_DOWN:
return {'move': (0,1)}
elif key.vk == tc.KEY_LEFT:
return {'move':(-1,0)}
elif key.vk == tc.KEY_RIGHT:
return {'move':(1,0)}
if key.vk == tc.KEY_ENTER and key.lalt: key = event.sym
return {'fullscreen':True}
elif key.vk == tc.KEY_ESCAPE:
return {'exit': True}
return {} if key == tcod.event.K_UP:
action = MovementAction(dx=0, dy=-1)
elif key == tcod.event.K_DOWN:
action = MovementAction(dx=0, dy=1)
elif key == tcod.event.K_LEFT:
action = MovementAction(dx=-1, dy=0)
elif key == tcod.event.K_RIGHT:
action = MovementAction(dx=1, dy=0)
elif key == tcod.event.K_ESCAPE:
action = EscapeAction()
return action

57
main.py Normal file
View File

@@ -0,0 +1,57 @@
import tcod
from engine import Engine
from entity import Entity
from game_map import GameMap
from input_handlers import EventHandler
def main():
screen_width = 80
screen_height = 50
map_width = 80
map_height = 45
room_max_size = 10
room_min_size = 6
max_rooms = 30
colors = {
'dark_wall': tcod.Color(0, 0, 100),
'dark_ground': tcod.Color(50, 50, 150)
}
tileset = tcod.tileset.load_tilesheet(
"arial10x10.png", 32,8, tcod.tileset.CHARMAP_TCOD
)
event_handler = EventHandler()
player = Entity(int(screen_width/2), int(screen_height/2), '@', (255, 255, 255))
npc = Entity(int(screen_width/2), int(screen_height/2), '@', (255, 255, 0))
entities = {npc, player}
game_map = GameMap(map_width, map_height)
engine = Engine(entities=entities, event_handler=event_handler, game_map=game_map, player=player)
with tcod.context.new_terminal(
screen_width,
screen_height,
tileset=tileset,
title='Roguelike Tutorial',
vsync=True,
) as context:
root_console = tcod.Console(screen_width,screen_height, order="F")
while True:
engine.render(console=root_console, context=context)
events = tcod.event.wait()
engine.handle_events(events)
if __name__ == '__main__':
main()

37
tile_types.py Normal file
View File

@@ -0,0 +1,37 @@
from typing import Tuple
import numpy as np
graphic_dt = np.dtype(
[
("ch", np.int32),
("fg", "3b"),
("bg", "3B"),
]
)
tile_dt = np.dtype(
[
("walkable", np.bool),
("transparent", np.bool),
("dark", graphic_dt),
]
)
def new_tile(
*,
walkable: int,
transparent: int,
dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
) -> np.ndarray:
"""Helper function for defining individual tile types"""
return np.array((walkable, transparent, dark), dtype=tile_dt)
floor = new_tile(
walkable=True, transparent=True, dark=(ord(" "),(255,255,255), (50,50, 150)),
)
wall = new_tile(
walkable=False, transparent=True, dark=(ord(" "), (255,255,255), (0,0,100)),
)