import curses
import sys
import random
import numpy as np
WALL = ' # '
SPACE = ' . '
class Player :
def __init__ ( self , x , y ) :
self . x = x
self . y = y
self . symbol = ' @ '
self . name = ' You '
self . hp = 10
self . damage = 2
class Enemy :
def __init__ ( self , x , y , symbol ) :
self . x = x
self . y = y
self . symbol = symbol
self . name = ' Bat '
self . hp = 5
self . damage = 1
class Map :
def __init__ ( self , width , height ) :
self . width = width
self . height = height
grid = self . make_grid ( )
dead_ends = self . hunt_and_kill ( grid )
grid = self . sample_rooms ( grid , dead_ends , 4 , int ( len ( dead_ends ) * 0.6 ) )
self . hunt_and_kill ( grid )
self . render_map ( grid )
def spawn ( self ) :
while True :
y = random . randrange ( 0 , self . height )
x = random . randrange ( 0 , self . width )
if self . map [ y ] [ x ] == SPACE :
return x , y
def sample_rooms ( self , grid , dead_ends , size , count ) :
grid = self . make_grid ( )
for x , y in random . sample ( dead_ends , count ) :
if x < self . width - size and y < self . height - size :
self . make_room ( grid , x , y , size )
return grid
def make_grid ( self ) :
return np . full ( ( self . height , self . width ) , WALL , dtype = str )
def make_room ( self , grid , x , y , size ) :
for row in range ( y , y + size ) :
for col in range ( x , x + size ) :
grid [ row ] [ col ] = SPACE
def find_coord ( self , grid ) :
for y in range ( 1 , self . height , 2 ) :
for x in range ( 1 , self . width , 2 ) :
if grid [ y ] [ x ] != WALL : continue
found = self . neighbors ( grid , x , y )
for found_x , found_y in found :
if grid [ found_y ] [ found_x ] == SPACE :
return [ [ x , y ] , [ found_x , found_y ] ]
return None
def inbounds ( self , x , y ) :
return x > = 0 and x < self . width and y > = 0 and y < self . height
def neighbors ( self , grid , x , y ) :
points = [ [ x , y - 2 ] ,
[ x , y + 2 ] ,
[ x - 2 , y ] ,
[ x + 2 , y ] ]
result = [ ]
for x , y in points :
if self . inbounds ( x , y ) :
result . append ( [ x , y ] )
return result
def neighbor_walls ( self , grid , x , y ) :
neighbors = self . neighbors ( grid , x , y )
result = [ ]
for x , y in neighbors :
if grid [ y ] [ x ] == WALL :
result . append ( [ x , y ] )
return result
def hunt_and_kill ( self , grid ) :
on_x = 1
on_y = 1
dead_ends = [ ]
while True :
n = self . neighbor_walls ( grid , on_x , on_y )
if len ( n ) == 0 :
dead_ends . append ( [ on_x , on_y ] )
t = self . find_coord ( grid )
if t == None : break
on_x , on_y = t [ 0 ]
found_x , found_y = t [ 1 ]
grid [ on_y ] [ on_x ] = SPACE
row = ( on_y + found_y ) / / 2
col = ( on_x + found_x ) / / 2
grid [ row ] [ col ] = SPACE
else :
nb_x , nb_y = random . choice ( n )
grid [ nb_y ] [ nb_x ] = SPACE
row = ( nb_y + on_y ) / / 2
col = ( nb_x + on_x ) / / 2
grid [ row ] [ col ] = SPACE
on_x , on_y = nb_x , nb_y
return dead_ends
def render_map ( self , grid ) :
self . map = [ ]
for y , y_line in enumerate ( grid ) :
cur_row = " "
for x , char in enumerate ( y_line ) :
cur_row + = char
self . map . append ( cur_row )
def collision ( self , target_x , target_y ) :
# remember this is True==COLLIDE WITH WALL, False=CAN MOVE THERE
return self . map [ target_y ] [ target_x ] == ' # '
def draw ( self , win ) :
for y , row in enumerate ( self . map ) :
win . addstr ( y , 0 , " " . join ( row ) )
class UI :
def __init__ ( self , stdscr , height , width , status_height ) :
curses . curs_set ( 0 )
stdscr . clear ( )
begin_x = 0
begin_y = 0
win = curses . newwin ( height , width , begin_y , begin_x )
win . keypad ( True )
status = win . subwin ( status_height , width , height - status_height , begin_x )
# keep these for later by assigning to self
self . begin_x = 0
self . begin_y = 0
self . map = None
self . height = height
self . width = width
self . win = win
self . status = status
self . status_msg = " HAVE FUN! "
self . status_height = status_height
def set_map ( self , the_map ) :
self . map = the_map
def update ( self , actors ) :
assert self . map , " You forgot to call set_map() "
self . win . clear ( )
self . status . box ( )
self . map . draw ( self . win )
# this assumes actors[0] is the player
self . draw_status ( actors )
for actor in actors :
self . draw_actor ( actor )
self . win . refresh ( )
def post_status ( self , msg ) :
self . status_msg = msg
def draw_status ( self , actors ) :
self . status . addstr ( 1 , 1 , self . status_msg )
def draw_actor ( self , actor ) :
assert self . map . map [ actor . y ] [ actor . x ] != ' # ' , f " WHAT? actor at { actor . x } , { actor . y } but that ' s a wall! "
# actor has to be moved in by 1 for the border
self . win . addstr ( actor . y , actor . x , actor . symbol , curses . A_BOLD )
def handle_input ( self , x , y ) :
ch = self . win . getch ( )
if ch == ord ( ' q ' ) :
sys . exit ( 0 )
elif ch == curses . KEY_UP :
y = ( y - 1 ) % self . height
elif ch == curses . KEY_DOWN :
y = ( y + 1 ) % self . height
elif ch == curses . KEY_RIGHT :
x = ( x + 1 ) % self . width
elif ch == curses . KEY_LEFT :
x = ( x - 1 ) % self . width
return x , y
class GameEngine :
def __init__ ( self , ui ) :
self . ui = ui
self . map = Map ( ui . width , ui . height - ui . status_height )
self . ui = ui
ui . set_map ( self . map )
def death ( self , target ) :
self . actors . remove ( target )
self . ui . post_status ( f " Killed { target . name } " )
def combat ( self , actor , target ) :
target . hp - = actor . damage
if target . hp > 0 :
self . ui . post_status ( f " HIT { target . name } for { actor . damage } " )
else :
self . death ( target )
def actor_collision ( self , actor , x , y ) :
for target in self . actors :
if target != actor and target . x == x and target . y == y :
return target
return None
def collision ( self , actor , x , y ) :
if self . map . collision ( x , y ) : return True
target = self . actor_collision ( actor , x , y )
if target :
self . combat ( actor , target )
return True
return False
def move_actor ( self , actor , x , y ) :
if not self . collision ( actor , x , y ) :
actor . x = x
actor . y = y
def spawn_actors ( self , enemy_count ) :
x , y = self . map . spawn ( )
self . player = Player ( x , y )
self . actors = [ self . player ]
for i in range ( 0 , enemy_count ) :
x , y = self . map . spawn ( )
enemy = Enemy ( x , y , ' { ' )
self . actors . append ( enemy )
def run ( self ) :
self . spawn_actors ( 5 )
while True :
# remember, first one has to be the player
self . ui . update ( self . actors )
new_x , new_y = self . ui . handle_input ( self . player . x , self . player . y )
self . move_actor ( self . player , new_x , new_y )
def main ( stdscr ) :
width = 27
height = 16
ui = UI ( stdscr , height , width , 5 )
game = GameEngine ( ui )
game . run ( )
curses . wrapper ( main )