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 render_functions import *
from game_map import *
from input_handlers import handle_keys
from game_map import GameMap
from input_handlers import EventHandler
def main():
screen_width = 80
screen_height = 50
class Engine:
def __init__(self, entities: Set[Entity], event_handler: EventHandler, game_map: GameMap, player: Entity):
self.entities = entities
self.event_handler = event_handler
self.game_map = game_map
self.player = player
map_width = 80
map_height = 50
def handle_events(self, events: Iterable[Any]) -> None:
for event in events:
action = self.event_handler.dispatch(event)
room_max_size = 10
room_min_size = 6
max_rooms = 30
if action is None:
continue
colors = {
'dark_wall': tc.Color(0, 0, 100),
'dark_ground': tc.Color(50, 50, 150)
}
action.perform(self, self.player)
player = Entity(int(screen_width/2), int(screen_height/2), '@', tc.white)
npc = Entity(int(screen_width/2), int(screen_height/2), '@', tc.yellow)
entities = [npc, player]
def render(self, console: Console, context: Context) -> None:
self.game_map.render(console)
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)
con = tc.console_new(screen_width, screen_height)
context.present(console)
game_map = GameMap(map_width, map_height)
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()
console.clear()

View File

@@ -1,14 +1,16 @@
from typing import Tuple
class Entity:
"""
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.y = y
self.char = char
self.color = color
def move(self, dx, dy):
def move(self, dx: int, dy: int) -> None:
self.x += dx
self.y += dy

View File

@@ -1,90 +1,18 @@
from map_objects import *
from random import randint
import numpy as np
from tcod.console import Console
import tile_types
class GameMap:
def __init__(self, width, height):
self.width = width
self.height = height
self.tiles = self.initialize_tiles()
def __init__(self, width: int, height: int):
self.width, self.height = width, height
self.tiles = np.full((width,height), fill_value=tile_types.floor, order="F")
def initialize_tiles(self):
tiles = [[Tile(True) for y in range(self.height)] for x in range(self.width)]
self.tiles[30:33, 22] = tile_types.wall
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):
if self.tiles[x][y].blocked:
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
def render(self, console: Console) -> None:
console.tiles_rgb[0:self.width, 0:self.height] = self.tiles["dark"]

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):
if key.vk == tc.KEY_UP:
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)}
def ev_keydown(self, event: "tcod.event.KeyDown") -> Optional[Action]:
action: Optional[Action] = None
if key.vk == tc.KEY_ENTER and key.lalt:
return {'fullscreen':True}
elif key.vk == tc.KEY_ESCAPE:
return {'exit': True}
key = event.sym
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)),
)