roguepy/game.py

179 lines
6.3 KiB
Python
Raw Normal View History

2023-03-01 19:47:24 +00:00
from terminal import Terminal
2023-03-22 17:17:44 +00:00
from entity import ITEMS_DATA, Creature, Player, Enemy
2023-03-01 19:47:24 +00:00
from actions import *
2023-03-20 20:12:05 +00:00
from floor import COLLIDABLE, Floor, TileType
2023-03-13 18:46:29 +00:00
import sys
2023-03-01 19:47:24 +00:00
TEXTURES = {
TileType.WALL: '#',
TileType.AIR: '.',
2023-03-20 20:12:05 +00:00
Player: '@',
Enemy: 'Y'
2023-03-01 19:47:24 +00:00
}
class Game:
def __init__(self):
self.term = Terminal()
self.player = Player()
self.floor = Floor(20, 10)
self.should_exit = False
2023-03-02 16:51:17 +00:00
self.ticks = 0
self.schedule = {}
2023-03-13 18:46:29 +00:00
self.redirect_io('prints.log', 'errors.log')
2023-03-21 18:36:19 +00:00
self.hud_height = 3
self.menu_width = 20
self.camera = (0, 0)
self.hud = 0
2023-03-13 18:46:29 +00:00
def __del__(self):
self.restore_io()
def restore_io(self) -> None:
sys.stdout = self.stdout_original
sys.stderr = self.stderr_original
self.stdout_file.close()
self.stderr_file.close()
def redirect_io(self, stdout_file, stderr_file) -> None:
self.stdout_original = sys.stdout
self.stderr_original = sys.stderr
self.stdout_file = open(stdout_file, 'w')
self.stderr_file = open(stderr_file, 'w')
sys.stdout = self.stdout_file
sys.stderr = self.stderr_file
2023-03-01 19:47:24 +00:00
def run(self):
"""
Runs the game
"""
2023-03-13 18:46:29 +00:00
self.instance_creature(3, 3, self.player)
2023-03-21 18:36:19 +00:00
self.instance_creature(6, 4, Enemy("slime"))
2023-03-22 17:17:44 +00:00
self.player.equip("shiv", "main_hand")
2023-03-02 17:08:02 +00:00
self.render()
2023-03-01 19:47:24 +00:00
while not self.should_exit:
2023-03-13 18:46:29 +00:00
self.step()
if self.action_was_performed:
2023-03-02 17:08:02 +00:00
self.render()
2023-03-02 16:51:17 +00:00
2023-03-13 18:46:29 +00:00
def instance_creature(self, x, y, creature: Creature):
2023-03-02 17:00:42 +00:00
"""
2023-03-13 18:46:29 +00:00
Instances a creature in space and time
2023-03-02 17:00:42 +00:00
"""
2023-03-13 18:46:29 +00:00
self.floor.entities.add_creature(x, y, creature)
self.reschedule(creature)
2023-03-02 16:51:17 +00:00
2023-03-13 18:46:29 +00:00
def reschedule(self, creature: Creature):
2023-03-02 16:51:17 +00:00
"""
2023-03-13 18:46:29 +00:00
Calculates the ticks in which the creature will perform its next action
2023-03-02 16:51:17 +00:00
"""
2023-03-13 18:46:29 +00:00
time = (ACTION_TIMING[creature.action.__class__] // creature.speed) + self.ticks
2023-03-02 16:51:17 +00:00
if self.schedule.get(time):
2023-03-13 18:46:29 +00:00
self.schedule[time].append(creature)
2023-03-02 16:51:17 +00:00
else:
2023-03-13 18:46:29 +00:00
self.schedule[time] = [creature]
2023-03-01 19:47:24 +00:00
def render(self):
"""
Renders the game
"""
2023-03-21 18:36:19 +00:00
(term_width, term_height) = self.term.get_dimensions()
(floor_frame_width, floor_frame_height) = (term_width - self.menu_width, term_height - self.hud_height)
self.term.clear()
# Floor
for x in range(min(self.floor.width, floor_frame_width)):
for y in range(min(self.floor.height, floor_frame_height)):
2023-03-02 16:51:17 +00:00
if (x, y) in self.floor.entities.pos_creatures:
2023-03-20 20:12:05 +00:00
self.term.put_char(x, y, TEXTURES[self.floor.entities.pos_creatures[(x,y)].__class__])
2023-03-02 16:51:17 +00:00
elif (x, y) in self.floor.entities.items:
2023-03-01 19:47:24 +00:00
self.term.put_char(x, y, 'i')
else:
self.term.put_char(x, y, TEXTURES[self.floor.get_tile(x, y)])
2023-03-21 18:36:19 +00:00
# HUD
for x in range(floor_frame_width):
for y in range(floor_frame_height, term_height):
self.term.put_char(x, y, ' ')
2023-03-22 17:17:44 +00:00
self.term.put_string(0, floor_frame_height, f"HP: {self.player.hp}/{self.player.max_hp}" )
self.term.put_string(0, floor_frame_height + 1, f"MH: {'None' if not self.player.equipment['main_hand'] else self.player.equipment['main_hand']['name']}; Damage: {self.player.total_damage()}")
2023-03-21 18:36:19 +00:00
# Menu
for x in range(floor_frame_width, term_width):
for y in range(term_height):
2023-03-22 17:17:44 +00:00
self.term.put_char(x, y, ' ')
self.term.put_string(floor_frame_width, 0, f"Move: {KEYBINDS['move_left']} {KEYBINDS['move_up']} {KEYBINDS['move_right']} {KEYBINDS['move_down']}")
self.term.put_string(floor_frame_width, 1, f"Inventory: {KEYBINDS['inventory']}")
self.term.put_string(floor_frame_width, 2, f"Quit: {KEYBINDS['quit']}")
2023-03-21 18:36:19 +00:00
2023-03-01 19:47:24 +00:00
def step(self):
2023-03-02 17:08:02 +00:00
"""
2023-03-13 18:46:29 +00:00
Perfoms a step in the game. Sets the `action_was_performed` attribute accordingly
2023-03-02 17:08:02 +00:00
"""
2023-03-13 18:46:29 +00:00
self.action_was_performed = False
creatures = self.schedule.get(self.ticks)
if creatures:
2023-03-20 20:12:05 +00:00
while creature := creatures[0] if len(creatures) > 0 else None:
2023-03-13 18:46:29 +00:00
if isinstance(creature, Player):
self.input_event()
elif isinstance(creature, Enemy):
creature.calculate_action()
2023-03-20 20:12:05 +00:00
if self.perform(creature):
creatures.pop(0)
2023-03-02 16:51:17 +00:00
self.schedule.pop(self.ticks)
2023-03-13 18:46:29 +00:00
self.action_was_performed = True
self.ticks += 1
2023-03-02 17:08:02 +00:00
2023-03-13 18:46:29 +00:00
def perform(self, creature: Creature):
2023-03-01 19:47:24 +00:00
"""
2023-03-02 16:51:17 +00:00
Performs an action for a creature
2023-03-01 19:47:24 +00:00
"""
2023-03-13 18:46:29 +00:00
if isinstance(creature.action, Move):
pos = self.floor.entities.pop_creature_ref(creature)
2023-03-02 16:51:17 +00:00
if pos:
2023-03-20 20:12:05 +00:00
(dest_x, dest_y) = (pos[0] + creature.action.x, pos[1] + creature.action.y)
out_of_bounds = dest_x < 0 or dest_x > self.floor.width-1 or dest_y < 0 or dest_y > self.floor.height-1
if out_of_bounds \
or self.floor.get_tile(dest_x, dest_y) in COLLIDABLE \
or not self.floor.entities.add_creature(dest_x, dest_y, creature):
self.floor.entities.add_creature(pos[0], pos[1], creature)
return False
self.reschedule(creature)
return True
2023-03-01 19:47:24 +00:00
2023-03-13 18:46:29 +00:00
def input_event(self):
2023-03-01 19:47:24 +00:00
"""
2023-03-13 18:46:29 +00:00
Waits for a game event to happen, and behaves accordingly
2023-03-01 19:47:24 +00:00
"""
keycode = self.term.get_key()
2023-03-22 17:17:44 +00:00
if keycode == KEYBINDS["quit"]:
2023-03-01 19:47:24 +00:00
self.should_exit = True
2023-03-22 17:17:44 +00:00
elif keycode == KEYBINDS["inventory"]:
2023-03-21 18:36:19 +00:00
self.hud += 1
print(self.hud, flush=True)
self.input_event()
2023-03-22 17:17:44 +00:00
elif keycode == KEYBINDS["move_right"]:
2023-03-01 19:47:24 +00:00
self.player.action = Move(1, 0)
2023-03-22 17:17:44 +00:00
elif keycode == KEYBINDS["move_left"]:
2023-03-01 19:47:24 +00:00
self.player.action = Move(-1, 0)
2023-03-22 17:17:44 +00:00
elif keycode == KEYBINDS["move_down"]:
2023-03-01 19:47:24 +00:00
self.player.action = Move(0, 1)
2023-03-22 17:17:44 +00:00
elif keycode == KEYBINDS["move_up"]:
self.player.action = Move(0, -1)
elif keycode == "KEY_RESIZE":
self.render()
self.input_event()
KEYBINDS = {
"move_right": "",
"move_left": "",
"move_down": "",
"move_up": "",
"quit": "q",
"inventory": "i",
}