I added some Utterly Mad specific touches to it, as well. Once I get over my most recent Steam acquisitions I'll likely get around to completing it.
And this one is written in Python, which is about the second best language you can use. I went with python because I was already kinda familiar with it thanks to our CMS at work - even if you aren't writing much, looking at enough code will give you an idea of how it works IMO. It also has the "advantage" of not needing to be compiled to run, which maybe sucks for some applications, but is good for me as compiling *anything* on my home computer sucks.
It runs off the libtcod library, which does most everything I need it to do. It has mouse support, which when I started was nice as it was much easier to use for looking and targeting than keyboard commands, though I do prefer the latter. The biggest issue with most older RLs (and Cata) is that they use curses, which isn't a very good library from my understanding. Libtcod does pretty much anything I could think of wanting featurewise, from having built in FOV to sound support, to the aformentioned mouse support, as well as being easy to make work with other stuff.
A very early version of the basic loop and associated code is below. Astute observers will notice it's very similar to the code in the libtcodpy tutorial (probably nearly identical at that point) - that's because you have to start somewhere, right? I don't have later, more unique and interesting versions here at work at the moment. While i previously did most of the coding here, something went wrong in the version pasted below, and it refuses to recognize the constants defined at the start of the code. I have no idea how to fix them as it will find something else to refuse to recognize even if I remove the new lines since the last stable run. That's really why I stopped working on it, because my more advanced version at home ran into the same problem eventually. At 700 odd lines, it's annoying. At 1800 it makes you not want to put more effort into it.
Like I say, I'll someday find motivation to actually learn more about what I'm doing and fix it. Until then, you can play what amounts to basically a tech demo of the game (everything else works mechanics-wise, though it's a little bare bones, by 0.11.3 which is this version, though IIRC monster-pathing is frustratingly defective as they don't move diagonally around doors yet, etc. etc.) if you download the library and Python.
import libtcodpy as libtcod
import math
import textwrap
SCREEN_WIDTH = 80
SCREEN_HEIGHT = 50
MAP_WIDTH = 80
MAP_HEIGHT = 43
#dungeon gen parameters
ROOM_SIZE_MAX = 10
ROOM_SIZE_MIN = 6
MAX_ROOMS = 30
MAX_ROOM_MOOKS = 3
MAX_ROOM_ITEMS = 3
#GUI stuff
BAR_WIDTH = 20
PANEL_HEIGHT = 7
PANEL_Y = SCREEN_HEIGHT - PANEL_HEIGHT
MSG_X = BAR_WIDTH + 2
MSG_WIDTH = SCREEN_WIDTH - BAR_WIDTH - 2
MSG_HEIGHT = PANEL_HEIGHT - 1
INVENTORY_WIDTH = 50
#spell values
HEAL_AMOUNT = libtcod.random_get_int(0, 10, 20)
LIGHTNING_RANGE = 5
LIGHTNING_DAMAGE = libtcod.random_get_int(0, 18, 25)
CONFUSE_NUM_TURNS = libtcod.random_get_int(0, 3, 10)
FIREBALL_RADIUS = 3
FIREBALL_DAMAGE = 12
#FOV stuff. This basically uses the default values built into libtcodpy
FOV_ALGO = 0
FOV_LIGHT_WALLS = True
TORCH_RADIUS = 10 #set this absurdly high for "dev mode" to explore quicker. easier than yanking FOV every time.
LIMIT_FPS = 10
color_dark_wall = libtcod.Color(150, 150, 150)
color_light_wall = libtcod.Color(100, 100, 100)
color_dark_ground = libtcod.Color(100, 100, 100)
color_light_ground = libtcod.Color(50, 50, 50)
#This uses tabbing because my spacebar doesn't work right
#########
#Classes#
#########
class Tile:
#defines a tile. If blocked, defaults to blocking sight
def __init__(self, blocked, block_sight = None):
self.blocked = blocked
#defaults to unexplored
self.explored = False
if block_sight is None: block_sight = blocked
self.block_sight = block_sight
class Rect:
#just a rectangle, used to represent a room
def __init__(self, x, y, w, h):
self.x1 = x
self.y1 = y
self.x2 = x + w
self.y2 = y + h
def center(self):
center_x = (self.x1 + self.x2) /2
center_y = (self.y1 + self.y2) /2
return (center_x, center_y)
def intersect(self, other):
#causes intersection to return true
return (self.x1 <= other.x2 and self.x2 >= other.x1 and
self.y1 <= other.y2 and self.y2 >= other.y1)
class Object:
#just a generic object
def __init__(self, x, y, char, name, color, blocks=False, combatant=None, ai=None, item=None):
self.x = x
self.y = y
self.char = char
self.name = name
self.color = color
self.blocks = blocks
self.combatant = combatant
#components
if self.combatant:
self.combatant.owner = self
self.ai = ai
if self.ai:
self.ai.owner = self
self.item = item
if self.item:
self.item.owner = self
def move(self, dx, dy):
#move by given amount if you aren't up against a wall or something
if not is_blocked(self.x + dx, self.y + dy):
self.x += dx
self.y += dy
def move_towards(self, target_x, target_y):
#vector and distance from object to target
dx = target_x - self.x
dy = target_y - self.y
distance = math.sqrt(dx ** 2 + dy ** 2)
#insures one tile movement and keeps in on the map grid
dx = int(round(dx / distance))
dy = int(round(dy / distance))
self.move(dx, dy)
def distance_to(self, other): #distance to mook or to player
dx = other.x - self.x
dy = other.y - self.y
return math.sqrt(dx ** 2 + dy ** 2)
def distance(self, x, y): #distance to specific co-ords
return math.sqrt((x - self.x) ** 2 + (y - self.y) ** 2)
def send_to_back(self):
#makes sure shit doesn't get drawn on top
global objects
objects.remove(self)
objects.insert(0, self)
def draw(self):
#setting color, then drawing char for object
if libtcod.map_is_in_fov(fov_map, self.x, self.y):
libtcod.console_set_default_foreground(con, self.color)
libtcod.console_put_char(con, self.x, self.y, self.char, libtcod.BKGND_NONE)
def clear(self):
#anti-chemtrail shit
libtcod.console_put_char(con, self.x, self.y, ' ', libtcod.BKGND_NONE)
class Combatant:
#combat related properties for all!
def __init__(self, hp, defense, power, death_function=None):
self.max_hp = hp
self.hp = hp
self.defense = defense
self.power = power
self.death_function = death_function
def attack(self, target):
#really basic damage formula
damage = self.power - target.combatant.defense
if damage > 0:
#if damage is above 0, you get damaged
message(self.owner.name.capitalize() + ' attacks ' + target.name + ' for ' + str(damage) + ' hitpoints!')
target.combatant.take_damage(damage)
else:
message(self.owner.name.capitalize() + ' attacks ' + target.name + ' but it is completely ineffective!')
def take_damage(self, damage):
if damage > 0:
self.hp -= damage
#kills you if you have no HP
if self.hp <= 0:
function = self.death_function
if function is not None:
function(self.owner)
def heal(self, amount):
#heal by given amount
self.hp += amount
if self.hp > self.max_hp:
self.hp = self.max_hp
class BasicMook:
#AI for a mook
def take_turn(self):
#takes turn. right now, everything has the same FOV.
mook = self.owner
if libtcod.map_is_in_fov(fov_map, mook.x, mook.y):
#moves towards target if it sees you
if mook.distance_to(player) >= 2:
mook.move_towards(player.x, player.y)
#attacks if close
elif player.combatant.hp > 0:
mook.combatant.attack(player)
class ConfusedMook:
#changes AI for confusion and confusion-like states
def __init__(self, old_ai, num_turns=CONFUSE_NUM_TURNS):
self.old_ai = old_ai
self.num_turns = num_turns
def take_turn(self):
if self.num_turns > 0: #any number above 0 returns still confused
#walks around randomly via rnd int x y coords
self.owner.move(libtcod.random_get_int(0, -1, 1), libtcod.random_get_int(0, -1, 1))
self.num_turns -= 1 #subtracts 1 from turns remaining. constant is up top for overall length
else: #stop being confused and revert to normal AI
self.owner.ai = self.old_ai
message("The " + self.owner.name + " gives it's head a shake and regains it's senses!", libtcod.red)
class Item:
#pretty obvious what this is
def __init__(self, use_function=None):
self.use_function = use_function
#pickups
def pick_up(self):
if len(inventory) >= 26:
message('You have too much shit and not enough pockets. You drop the ' + self.owner.name + '.', libtcod.red)
else:
inventory.append(self.owner)
objects.remove(self.owner)
if libtcod.random_get_int(0, 0, 100) < 85:
message("You pick up a " + self.owner.name + "!", libtcod.green)
else:
if libtcod.random_get_int(0, 0, 1) == 1:
message("You decide you need to pick up yet another " + self.owner.name + ", probably because you have a hoarding issue.", libtcod.green)
else:
message("You decide to steal my " + self.owner.name + ", apparently because you're an asshole.", libtcod.green)
def use(self):
#calls use_function if defined
if self.use_function is None:
message("The " + self.owner.name + " can't be used.")
else:
if self.use_function() != "cancelled":
inventory.remove(self.owner) #destroys after use
###########
#Some Defs#
###########
def message(new_msg, color = libtcod.white):
#textwraps if long
new_msg_lines = textwrap.wrap(new_msg, MSG_WIDTH)
#removes lines if display full
for line in new_msg_lines:
if len(game_msgs) == MSG_HEIGHT:
del game_msgs[0]
#then adds a new line
game_msgs.append( (line, color) )
def is_blocked(x, y):
#checks to see if placement is blocked
if map[x][y].blocked:
return True
for object in objects:
if object.blocks and object.x == x and object.y == y:
return True
return False
def create_room(room):
global map
#chops out tiles in the rect and makes them unblocked
for x in range(room.x1 + 1, room.x2):
for y in range(room.y1 + 1, room.y2):
map[x][y].blocked = False
map[x][y].block_sight = False
def create_h_tunnel(x1, x2, y):
global map
#chops out horizontal tunnels
for x in range(min(x1, x2), max(x1, x2) + 1):
map[x][y].blocked = False
map[x][y].block_sight = False
def create_v_tunnel(y1, y2, x):
global map
#chops out vertical tunnels
for y in range(min(y1, y2), max(y1, y2) +1):
map[x][y].blocked = False
map[x][y].block_sight = False
#min/max prevents room overlap. It's a bit of a funky way to do this, but
#probably the least code intensive way.
def make_map():
global map, player
#fills with blocked tiles. Change to false for unblocked tiles
map = [[ Tile(True)
for y in range(MAP_HEIGHT) ]
for x in range (MAP_WIDTH) ]
rooms = []
num_rooms = 0
for r in range(MAX_ROOMS):
#randomize w & h
w = libtcod.random_get_int(0, ROOM_SIZE_MIN, ROOM_SIZE_MAX)
h = libtcod.random_get_int(0, ROOM_SIZE_MIN, ROOM_SIZE_MAX)
#random position while still on map
x = libtcod.random_get_int(0, 0, MAP_WIDTH - w - 1)
y = libtcod.random_get_int(0, 0, MAP_HEIGHT - h - 1)
#carves some rooms based on Rect class
new_room = Rect(x, y, w, h)
#make sure no rooms intersect
failed = False
for other_room in rooms:
if new_room.intersect(other_room):
failed = True
break
if not failed:
create_room(new_room)
#center co-ords of new room
(new_x, new_y) = new_room.center()
#defines first room and player start location
if num_rooms == 0:
player.x = new_x
player.y = new_y
else:
#this connects all rooms with a tunnels
#centering previous room
(prev_x, prev_y) = rooms[num_rooms-1].center()
#flips coin
if libtcod.random_get_int(0, 0, 1) == 1:
#goes horizontal, then vertical
create_h_tunnel(prev_x, new_x, prev_y)
create_v_tunnel(prev_y, new_y, new_x)
else:
#going vertical, then horizontal
create_v_tunnel(prev_y, new_y, prev_x)
create_h_tunnel(prev_x, new_x, new_y)
#runs object placement
place_objects(new_room)
#appends new room to list
rooms.append(new_room)
num_rooms += 1
##########
#Placing stuff
def place_objects(room):
#randomizes mook numbers
num_mooks = libtcod.random_get_int(0, 0, MAX_ROOM_MOOKS)
#random placement if tile isn't blocked
for i in range(num_mooks):
x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
y = libtcod.random_get_int(0, room.y1+1, room.y2-1)
if not is_blocked(x, y):
if libtcod.random_get_int(0, 0, 100) < 70: #70% chance of this mook
combatant_component = Combatant(hp=10, defense=0, power=3, death_function=mook_death)
ai_component = BasicMook()
mook = Object(x, y, 'T', 'Russian thug', libtcod.white, blocks=True,
combatant=combatant_component, ai=ai_component)
else:
#makes a different mook with the other 30%
combatant_component = Combatant(hp=15, defense=1, power=4, death_function=mook_death)
ai_component = BasicMook()
mook = Object(x,y, 'E', 'Russian enforcer', libtcod.white, blocks=True, combatant=combatant_component, ai=ai_component)
#adds them to the mook list
objects.append(mook)
#randomizes items
num_items = libtcod.random_get_int(0, 0, MAX_ROOM_ITEMS)
for i in range(num_items):
#random placement
x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
y = libtcod.random_get_int(0, room.y1+1, room.y2-1)
if not is_blocked(x, y):
dice = libtcod.random_get_int(0, 0, 100)
if dice < 70:
item_component = Item(use_function=cast_heal)
item = Object(x, y, '!', 'beer', libtcod.yellow, item=item_component)
elif dice < 70+10:
#lightning bolt scroll w/ 10% chance
item_component = Item(use_function=cast_lightning)
item = Object(x, y, "~", libtcod.yellow, item=item_component)
elif dice < 70+10+10:
#grenade w/ 10% chance
item_component = Item(use_function=cast_fireball)
item = Object(x, y, "*", "grenade", libtcod.dark_green, item=item_component)
else:
#create confuse item with other 10%
item_component = Item(use_function=cast_confuse)
item = Object(x, y, "!", "bottle of vodka", libtcod.light_gray, item=item_component)
objects.append(item)
item.send_to_back()
#renders stat bar
def render_bar(x, y, total_width, name, value, maximum, bar_color, back_color):
bar_width = int(float(value) / maximum * total_width)
#sets background
libtcod.console_set_default_background(panel, back_color)
libtcod.console_rect(panel, x, y, total_width, 1, False, libtcod.BKGND_SCREEN)
#renders on top
libtcod.console_set_default_background(panel, bar_color)
if bar_width > 0:
libtcod.console_rect(panel, x, y, bar_width, 1, False, libtcod.BKGND_SCREEN)
libtcod.console_set_default_foreground(panel, libtcod.white)
libtcod.console_print_ex(panel, x + total_width / 2, y, libtcod.BKGND_NONE, libtcod.CENTER,
name + ': ' + str(value) + '/' + str(maximum))
def get_names_under_mouse():
global mouse
#returns all names under mouse
(x, y) = (mouse.cx, mouse.cy)
names = [obj.name for obj in objects
if obj.x == x and obj.y == y and libtcod.map_is_in_fov(fov_map, obj.x, obj.y)]
names = ', '.join(names)
return names.capitalize()
def target_tile(max_range=None):
#returns left-clicked tile in FOV. checking for keypress throws away the keys pressed after activation but before clicking
#otherwise, they would store and run after lclick, which could be interesting as an effect of another item
global key, mouse
while True:
libtcod.console_flush()
libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS|libtcod.EVENT_MOUSE,key,mouse)
render_all()
(x, y) = (mouse.cx, mouse.cy)
if mouse.rbutton_pressed or key.vk == libtcod.KEY_ESCAPE:
return (None, None) #cancels. Must be none, none, or you get a crash error because it's a tuple
if (mouse.lbutton_pressed and libtcod.map_is_in_fov(fov_map, x, y) and
(max_range is None or player.distance(x, y) <= max_range)):
return: (x, y)
def menu(header, options, width):
#returns message if too many items on menu
if len(options) > 26: raise ValueError("Too many options")
#basic menu layout
header_height = libtcod.console_get_height_rect(con, 0, 0, width, SCREEN_HEIGHT, header)
height = len(options) + header_height
window = libtcod.console_new(width, height)
libtcod.console_set_default_foreground(window, libtcod.white)
libtcod.console_print_rect_ex(window, 0, 0, width, height, libtcod.BKGND_NONE, libtcod.LEFT, header)
#prints all options
y = header_height
letter_index = ord('a')
for option_text in options:
text = '(' + chr(letter_index) + ') ' + option_text
libtcod.console_print_ex(window, 0, y, libtcod.BKGND_NONE, libtcod.LEFT, text)
y += 1
letter_index += 1
#and then blits and waits for key input
x = SCREEN_WIDTH/2 - width/2
y = SCREEN_HEIGHT/2 - height/2
libtcod.console_blit(window, 0, 0, width, height, 0, x, y, 1.0, 0.7)
libtcod.console_flush()
key = libtcod.console_wait_for_keypress(True)
index = key.c - ord('a')
if index >= 0 and index < len(options): return index
return None
def inventory_menu(header):
#duh
if len(inventory) == 0:
options = ["You have no possessions, you commie bastard."]
else:
options = [item.name for item in inventory]
index = menu(header, options, INVENTORY_WIDTH)
if index is None or len(inventory) == 0: return None
return inventory[index].item
def render_all():
global color_dark_wall, color_light_wall
global color_dark_ground, color_light_ground
global fov_recompute
if fov_recompute:
fov_recompute = False
libtcod.map_compute_fov(fov_map, player.x, player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO)
for y in range(MAP_HEIGHT):
for x in range(MAP_WIDTH):
visible = libtcod.map_is_in_fov(fov_map, x, y)
wall = map[x][y].block_sight
#out of fov but explored
if not visible:
if map[x][y].explored:
if wall:
libtcod.console_put_char_ex(con, x, y, '#', color_light_wall, libtcod.BKGND_SET)
else:
libtcod.console_put_char_ex(con, x, y, '.', color_light_ground, libtcod.BKGND_SET)
#in fov
else:
if wall:
libtcod.console_put_char_ex(con, x, y, '#', color_dark_wall, libtcod.BKGND_SET)
else:
libtcod.console_put_char_ex(con, x, y, '.', color_dark_ground, libtcod.BKGND_SET)
#makes the game explore new visible tiles
map[x][y].explored = True
for object in objects:
if object != player:
object.draw()
player.draw()
#blitting. These numbers are saying the source windown has it's top left
#at 0, 0, the con is the same size as the screen, and the destination
#co-ords are the same as the source (0, 0)
libtcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)
#render GUI
libtcod.console_set_default_background(panel, libtcod.black)
libtcod.console_clear(panel)
#prints messages
y = 1
for (line, color) in game_msgs:
libtcod.console_set_default_foreground(panel, color)
libtcod.console_print_ex(panel, MSG_X, y, libtcod.BKGND_NONE, libtcod.LEFT, line)
y += 1
#show stats on GUI
render_bar(1, 1, BAR_WIDTH, 'HP', player.combatant.hp, player.combatant.max_hp,
libtcod.red, libtcod.dark_gray)
libtcod.console_set_default_foreground(panel, libtcod.light_gray)
libtcod.console_print_ex(panel, 1, 0, libtcod.BKGND_NONE, libtcod.LEFT, get_names_under_mouse())
libtcod.console_blit(panel, 0, 0, SCREEN_WIDTH, PANEL_HEIGHT, 0, 0, PANEL_Y)
def player_move_or_attack(dx, dy):
global fov_recompute
x = player.x + dx
y = player.y + dy
#check for target to attack
target = None
for object in objects:
if object.combatant and object.x == x and object.y == y:
target = object
break
#attack if target is found, or move
if target is not None:
player.combatant.attack(target)
else:
player.move(dx, dy)
fov_recompute = True
def closest_mook(max_range):
#locates the closest mook up to max range, if in FOV
closest_enemy = None
closest_dist = max_range + 1 #measures above max range to eliminate things further out
for object in objects:
if object.combatant and not object == player and libtcod.map_is_in_fov(fov_map, object.x, object.y):
dist = player.distance_to(object)
if dist < closest_dist:
closest_enemy = object
closest_dist = dist
return closest_enemy
#############
#Handle Keys#
#############
#Mouse support is handled by the libtcodpy library, and which is called mostly in the main loop.
def handle_keys():
global key
#fullscreen
if key.vk == libtcod.KEY_ENTER and key.lalt:
libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
elif key.vk == libtcod.KEY_ESCAPE:
return 'exit' #exits
if game_state == 'playing':
#move/attack keys
if key.vk == libtcod.KEY_UP:
player_move_or_attack(0, -1)
elif libtcod.console_is_key_pressed(libtcod.KEY_DOWN):
player_move_or_attack(0, 1)
elif libtcod.console_is_key_pressed(libtcod.KEY_LEFT):
player_move_or_attack(-1,0)
elif libtcod.console_is_key_pressed(libtcod.KEY_RIGHT):
player_move_or_attack(1, 0)
else:
#checks for non-move keys
key_char = chr(key.c)
if key_char == 'g':
#picks up shit
for object in objects:
if object.x == player.x and object.y == player.y and object.item:
object.item.pick_up()
if key_char == 'i':
#shows inventory menu
chosen_item = inventory_menu("Select an item to use.\n")
if chosen_item is not None:
chosen_item.use()
return 'no-turn'
###########
#More defs#
###########
def player_death(player):
#lets you die
global game_state
message('Another life claimed by the Madness...', libtcod.dark_red)
game_state = 'dead'
#turn you into a body beacause yes
player.char = '%'
player.color = libtcod.red
def mook_death(mook):
#kills mooks and such
message(mook.name.capitalize() + ' dies!')
mook.char = '%'
mook.color = libtcod.dark_red
mook.blocks = False
mook.combatant = None
mook.ai = None
mook.name = "What's left of " + mook.name
mook.send_to_back()
#########################
#Defs related to casting#
#########################
def cast_heal():
#heals. amount is defined with constants at top
if player.combatant.hp == player.combatant.max_hp:
message("You're already pretty hammered.", libtcod.red)
return "cancelled"
message("You drink the pain away.", libtcod.light_violet)
player.combatant.heal(HEAL_AMOUNT)
def cast_lightning():
#generic damage casting. Damage is defined with constants at top
mook = closest_mook(LIGHTNING_RANGE)
if mook is None: #ie nothing in range
message("Everyone is too far away.", libtcod.red)
return 'cancelled'
#somethig is present
message("A bolt of sound and fury strikes the " + mook.name + " for " + str(LIGHTNING_DAMAGE) + " HP, which really looks rather unpleasant.", libtcod.green)
mook.combatant.take_damage(LIGHTNING_DAMAGE)
def cast_confuse():
#finds closest target and confuses
mook = closest_mook(CONFUSE_RANGE)
if mook is None:
message("No one is close enough for this.", libtcod.red)
return 'cancelled'
old_ai = mook.ai
mook.ai = ConfusedMook(old_ai)
mook.ai.owner = mook
message("The vodka gets the " + mook.name + " white girl wasted, and it starts to stumble.", libtcod.green)
def cast_fireball():
#where?
message("Left click a tile or enemy to throw the grenade. Right click or escape to cancel.", libtcod.blue)
(x, y) = target_tile()
if x is None: return 'cancelled'
message("The grenade explodes, throwing deadly shrapnel at everything in " + str(FIREBALL_RADIUS) + " feet!", libtcod.blue)
for object in objects:
if obj.distance(x, y) <= FIREBALL_RADIUS and obj.combatant:
message("The shrapnel blasts through " + obj.name + ", dealing " + str(FIREBALL_DAMAGE) + " of damage!", libtcod.green)
obj.combatant.take_damage(FIREBALL_DAMAGE)
##############
#Initialize Window
##############
libtcod.console_set_custom_font('terminal10x10_gs_tc.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'Utter Madness', False)
libtcod.sys_set_fps(LIMIT_FPS)
con = libtcod.console_new(SCREEN_WIDTH, SCREEN_HEIGHT)
#puts stats up the side
panel = libtcod.console_new(SCREEN_WIDTH, PANEL_HEIGHT)
#placing objects
combatant_component = Combatant(hp=30, defense=2, power=9, death_function=player_death)
player = Object(0, 0, '@', 'apatheticexcuse', libtcod.white, blocks=True, combatant=combatant_component)
objects = [player]
make_map()
#sets up FOV
fov_map = libtcod.map_new(MAP_WIDTH, MAP_HEIGHT)
for y in range(MAP_HEIGHT):
for x in range(MAP_WIDTH):
libtcod.map_set_properties(fov_map, x, y, not map[x][y].block_sight, not map[x][y].blocked)
fov_recompute = True
game_state = 'playing'
player_action = None
inventory = []
game_msgs = []
message('Enter the Madness.')
mouse = libtcod.Mouse()
key = libtcod.Key()
########
#And The Main Loop
########
while not libtcod.console_is_window_closed():
libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS|libtcod.EVENT_MOUSE,key,mouse)
render_all()
libtcod.console_flush()
#anti-chemtrail always goes after draw & pretty much anything else
for object in objects:
object.clear()
player_action = handle_keys()
#exits
if player_action == 'exit':
break
#dictates turns
if game_state == 'playing' and player_action != 'no-turn':
for object in objects:
if object.ai:
object.ai.take_turn()
There's also a few good demonstrative projects available free that you can start learning with in Unity. I got reasonably far in their "follow along" tutorial there, but the computer I'm stuck with for now is too garbage to run Unity.