├── SDL.dll ├── CREDITS.txt ├── zlib1.dll ├── arial10x10.png ├── arial12x12.png ├── libtcod-VS.dll ├── config.py ├── LICENSE.txt ├── roguelike.spec ├── log.py ├── README.md ├── dist-windows.py ├── ai.py ├── miscellany.py ├── map.py ├── algebra.py ├── spells.py ├── components.py ├── interface.py ├── cartographer.py ├── actions.py ├── roguelike.py ├── renderer.py └── libtcodpy.py /SDL.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naburimannu/libtcodpy-tutorial/HEAD/SDL.dll -------------------------------------------------------------------------------- /CREDITS.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naburimannu/libtcodpy-tutorial/HEAD/CREDITS.txt -------------------------------------------------------------------------------- /zlib1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naburimannu/libtcodpy-tutorial/HEAD/zlib1.dll -------------------------------------------------------------------------------- /arial10x10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naburimannu/libtcodpy-tutorial/HEAD/arial10x10.png -------------------------------------------------------------------------------- /arial12x12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naburimannu/libtcodpy-tutorial/HEAD/arial12x12.png -------------------------------------------------------------------------------- /libtcod-VS.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naburimannu/libtcodpy-tutorial/HEAD/libtcod-VS.dll -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shared configuration variables. 3 | """ 4 | 5 | # Total size of the game window. 6 | SCREEN_WIDTH = 80 7 | SCREEN_HEIGHT = 50 8 | 9 | # Size of the map panel in the window. 10 | MAP_PANEL_WIDTH = 80 11 | MAP_PANEL_HEIGHT = 43 12 | 13 | # Size of the actual map; if larger than the map panel, the map panel will 14 | # scroll as the player moves. 15 | MAP_WIDTH = 60 16 | MAP_HEIGHT = 33 17 | 18 | # Height of the HUD display (should be screen height - map display height) 19 | PANEL_HEIGHT = 7 20 | BAR_WIDTH = 20 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Thomas C. Hudson 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /roguelike.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | 3 | # Attempts to configure PyInstaller for roguelike 4 | # libtcodpy *conditionally* imports numpy, but PyInstaller 5 | # can't seem to deal with that conditionality and makes it a 6 | # hard requirement. 7 | 8 | block_cipher = None 9 | 10 | fonts = [('arial12x12.png', '.')] 11 | 12 | tcod_dlls = [('libtcod-VS.dll', '.'), 13 | ('SDL.dll', '.'), 14 | ('zlib1.dll', '.')] 15 | 16 | options = [('v', None, 'OPTION'), ('u', None, 'OPTION')] 17 | 18 | a = Analysis(['roguelike.py'], 19 | pathex=['C:\\Projects\\libtcodpy-tutorial'], 20 | binaries=tcod_dlls, 21 | datas=fonts, 22 | hiddenimports=['encodings.codecs', 'encodings.encodings', 'encodings.__builtin__', 23 | 'ctypes.os', 'ctypes.sys', 'ctypes._ctypes', 'ctypes.struct'], 24 | hookspath=[], 25 | runtime_hooks=[], 26 | excludes=[], 27 | win_no_prefer_redirects=False, 28 | win_private_assemblies=False, 29 | cipher=block_cipher) 30 | pyz = PYZ(a.pure, a.zipped_data, 31 | cipher=block_cipher) 32 | exe = EXE(pyz, 33 | a.scripts, 34 | options, 35 | exclude_binaries=True, 36 | name='roguelike', 37 | debug=False, 38 | strip=False, 39 | upx=True, 40 | console=True ) 41 | coll = COLLECT(exe, 42 | a.binaries, 43 | a.zipfiles, 44 | a.datas, 45 | strip=False, 46 | upx=True, 47 | name='roguelike') 48 | -------------------------------------------------------------------------------- /log.py: -------------------------------------------------------------------------------- 1 | """ 2 | Global message log. 3 | 4 | Call log.init() before using. 5 | Retrieve (message, color) tuples from log.game_msgs[]. 6 | Append them using log.message(). 7 | """ 8 | 9 | import libtcodpy as libtcod 10 | import textwrap 11 | 12 | import config 13 | 14 | MSG_WIDTH = config.SCREEN_WIDTH - config.BAR_WIDTH - 2 15 | # Number of lines of messages normally displayed on screen 16 | MSG_HEIGHT = config.PANEL_HEIGHT - 1 17 | # Number of lines of messages retained for history display (^p) 18 | MSG_LIMIT = 150 19 | 20 | 21 | class ExplicitMessage(object): 22 | def __init__(self, message, color, count): 23 | self.message = message 24 | self.color = color 25 | self.count = count 26 | 27 | def can_merge(self, other): 28 | return (self.message == other.message and 29 | self.color == other.color and 30 | self.count + other.count < 10) 31 | 32 | 33 | def init(): 34 | global game_msgs 35 | 36 | # The list of game messages and their colors; starts empty. 37 | game_msgs = [] 38 | 39 | 40 | def message(new_msg, color=libtcod.white): 41 | """ 42 | Add a colored string to the end of the log; 43 | does wordwrap at MSG_WIDTH-5 characters 44 | since a count e.g. " (x3)" can add up to 5. 45 | """ 46 | global game_msgs 47 | new_msg_lines = textwrap.wrap(new_msg, MSG_WIDTH-5) 48 | 49 | for line in new_msg_lines: 50 | # If the buffer is full, remove the first line 51 | # to make room for the new one. 52 | if len(game_msgs) == MSG_LIMIT: 53 | del game_msgs[0] 54 | new_message = ExplicitMessage(line, color, 1) 55 | if game_msgs and game_msgs[-1].can_merge(new_message): 56 | game_msgs[-1].count += 1 57 | return 58 | game_msgs.append(new_message) 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libtcodpy-tutorial 2 | 3 | An extension of Jotaf's Python roguelike tutorial for libtcod. 4 | 5 | The code flows incredibly naturally from the tutorial, much like literate programming, but results in a single large file. 6 | In practice that feels to me like the "Big Ball of Mud" architecture; I want to be able to base development off of something cleaner. 7 | This refactoring starts with the same code and divides it up into 12 smaller files, all levelized: 8 | 9 | 1. roguelike: main menu, mainloop, load and save, player actions 10 | 2. cartographer: map generation 11 | 3. spells and targeting functions 12 | 4. interface 13 | 5. ai 14 | 6. actions: movement and combat (implementations shared between player and monsters) 15 | 7. renderer 16 | 8. components: Object, Fighter, Item, Equipment, AI 17 | 9. map 18 | 10. algebra: Rect, Location, Direction 19 | 11. log 20 | 12. config 21 | 22 | Most of the global variables have been eliminated; modules export renderer.overay and log.game_msgs, while renderer also uses a few "globals" internally. 23 | At the git tag 'basic-refactoring', the only other significant change should be: 24 | * Get rid of the Tile class for a 2x reduction in filesize and 2x speedup in load/save. 25 | 26 | Subsequently implemented externally-visible features include: 27 | * Persistent maps, and up-stairs allowing backtracking. 28 | * Support for maps smaller or larger than the screen. 29 | * ^p opens old log, which can be scrolled through with numpad, vi keys, or mousewheel. 30 | * Move with vi keys (hjkl,yubn) as well as numpad. 31 | * Running with shift-move until reaching an object, a change in architecture, or spotting a monster. 32 | * Targeting with keyboard as well as mouse. 33 | * Help screen on ? or F1. 34 | * ~2.5x speedup drawing large maps (from 6-7 fps to 15-17 fps for 200x200). 35 | * Stackable objects 36 | 37 | -------------------------------------------------------------------------------- /dist-windows.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create a Windows executable using py2exe. 3 | """ 4 | from distutils.core import setup 5 | import py2exe 6 | import os 7 | import sys 8 | 9 | sys.argv.append('py2exe') 10 | 11 | target_file = 'roguelike.py' 12 | 13 | assets_dir = '.\\' 14 | 15 | excluded_file_types = ['py', 'pyc', 'pyproj', 16 | 'sdf', 'sln', 'spec'] 17 | 18 | excluded_file_names = ['.gitattributes', '.gitignore', 'savegame'] 19 | 20 | excluded_directories = ['.git', '.vs'] 21 | 22 | def get_data_files(base_dir, target_dir, list=[]): 23 | """ 24 | " * get_data_files 25 | " * base_dir: The full path to the current working directory. 26 | " * target_dir: The directory of assets to include. 27 | " * list: Current list of assets. Used for recursion. 28 | " * 29 | " * returns: A list of relative and full path pairs. This is 30 | " * specified by distutils. 31 | """ 32 | for file in os.listdir(base_dir + target_dir): 33 | 34 | full_path = base_dir + target_dir + file 35 | if os.path.isdir(full_path): 36 | if (file in excluded_directories): 37 | continue 38 | get_data_files(base_dir, target_dir + file + '\\', list) 39 | elif os.path.isfile(full_path): 40 | if (len(file.split('.')) == 2 and file.split('.')[1] in excluded_file_types): 41 | continue 42 | if (file in excluded_file_names): 43 | continue 44 | list.append((target_dir, [full_path])) 45 | 46 | return list 47 | 48 | my_files = get_data_files(sys.path[0] + '\\', assets_dir) 49 | 50 | opts = { 'py2exe': { 51 | 'ascii':'True', 52 | 'excludes':['_ssl','_hashlib'], 53 | 'includes' : ['anydbm', 'dbhash'], 54 | 'bundle_files':'1', 55 | 'compressed':'True'}} 56 | 57 | setup(console=[target_file], 58 | data_files=my_files, 59 | zipfile=None, 60 | options=opts) -------------------------------------------------------------------------------- /ai.py: -------------------------------------------------------------------------------- 1 | """ 2 | AI routines, AI data, and monster death. 3 | """ 4 | import libtcodpy as libtcod 5 | 6 | import log 7 | from components import * 8 | import actions 9 | 10 | 11 | # Might make sense to have this defined 12 | # in spells.py instead, dropping the 13 | # default argument? 14 | CONFUSE_NUM_TURNS = 10 15 | 16 | 17 | class basic_monster_metadata: 18 | def __init__(self, target): 19 | self.target = target 20 | 21 | 22 | def basic_monster(monster, player, metadata): 23 | """ 24 | A basic monster takes its turn. if you can see it, it can see you. 25 | """ 26 | if libtcod.map_is_in_fov(monster.current_map.fov_map, 27 | monster.x, monster.y): 28 | if monster.distance_to(metadata.target) >= 2: 29 | actions.move_towards(monster, metadata.target.pos) 30 | elif metadata.target.fighter.hp > 0: 31 | actions.attack(monster.fighter, metadata.target) 32 | 33 | 34 | class confused_monster_metadata: 35 | def __init__(self, old_ai, num_turns=CONFUSE_NUM_TURNS): 36 | self.old_ai = old_ai 37 | self.num_turns = num_turns 38 | 39 | 40 | def random_direction(): 41 | return algebra.directions[libtcod.random_get_int(0, 0, 7)] 42 | 43 | 44 | def confused_monster(monster, player, metadata): 45 | if metadata.num_turns > 0: 46 | actions.move(monster, random_direction()) 47 | metadata.num_turns -= 1 48 | else: 49 | # Restore the previous AI (this one will be deleted 50 | # because it's not referenced anymore) 51 | monster.ai = metadata.old_ai 52 | log.message(monster.name.capitalize() + 53 | ' is no longer confused!', libtcod.red) 54 | 55 | 56 | def monster_death(monster): 57 | # Transform it into a nasty corpse! it doesn't block, can't be 58 | # attacked, and doesn't move. 59 | log.message( 60 | 'The ' + monster.name + ' is dead! You gain ' + 61 | str(monster.fighter.xp) + ' experience points.', libtcod.orange) 62 | monster.char = '%' 63 | monster.color = libtcod.dark_red 64 | monster.blocks = False 65 | monster.fighter = None 66 | monster.ai = None 67 | monster.name = 'remains of ' + monster.name 68 | monster.current_map.objects.remove(monster) 69 | monster.current_map.objects.insert(0, monster) 70 | -------------------------------------------------------------------------------- /miscellany.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Thomas C. Hudson 2 | # Governed by the license described in LICENSE.txt 3 | import libtcodpy as libtcod 4 | 5 | import log 6 | import algebra 7 | from components import * 8 | import actions 9 | import map 10 | import spells 11 | 12 | def dagger(): 13 | return Object(algebra.Location(0, 0), '-', 'dagger', libtcod.sky, 14 | item=Item(description='A leaf-shaped bronze knife; provides +2 Attack'), 15 | equipment=Equipment(slot='right hand', power_bonus=2)) 16 | 17 | def healing_potion(pos=algebra.Location(0, 0)): 18 | return Object(pos, '!', 'healing potion', libtcod.violet, 19 | item=Item(use_function=spells.cast_heal, 20 | description='A flask of revivifying alchemical mixtures; heals ' + str(spells.HEAL_AMOUNT) + ' hp.')) 21 | 22 | def lightning_scroll(pos=algebra.Location(0, 0)): 23 | return Object(pos, '#', 'scroll of lightning bolt', libtcod.light_yellow, 24 | item=Item(use_function=spells.cast_lightning, 25 | description='Reading these runes will strike your nearest foe with lightning for ' + 26 | str(spells.LIGHTNING_DAMAGE) + ' hp.')) 27 | 28 | def fireball_scroll(pos=algebra.Location(0, 0)): 29 | return Object(pos, '#', 'scroll of fireball', libtcod.light_yellow, 30 | item=Item(use_function=spells.cast_fireball, 31 | description='Reading these runes will cause a burst of flame inflicting ' + str(spells.FIREBALL_DAMAGE) + 32 | ' hp on nearby creatures.')) 33 | 34 | def confusion_scroll(pos=algebra.Location(0, 0)): 35 | return Object(pos, '#', 'scroll of confusion', libtcod.light_yellow, 36 | item=Item(use_function=spells.cast_confuse, 37 | description='Reading these runes will confuse the creature you focus on for a short time.')) 38 | 39 | def sword(pos=algebra.Location(0, 0)): 40 | return Object(pos, '/', 'sword', libtcod.sky, 41 | item=Item(description='A heavy-tipped bronze chopping sword; provides +3 Attack'), 42 | equipment=Equipment(slot='right hand', power_bonus=3)) 43 | 44 | def shield(pos=algebra.Location(0, 0)): 45 | return Object(pos, '[', 'shield', libtcod.darker_orange, 46 | item=Item(description='A bronze-edged oval shield; provides +1 Defense'), 47 | equipment=Equipment(slot='left hand', defense_bonus=1)) 48 | -------------------------------------------------------------------------------- /map.py: -------------------------------------------------------------------------------- 1 | import libtcodpy as libtcod 2 | 3 | import algebra 4 | 5 | 6 | class Room(algebra.Rect): 7 | def __init__(self, x, y, w, h): 8 | super(self.__class__, self).__init__(x, y, w, h) 9 | 10 | 11 | class Terrain(object): 12 | def __init__(self, name, display_name, icon, 13 | seen_color, unseen_color, blocks, blocks_sight): 14 | self.name = name 15 | self.display_name = display_name # text displayed on mouseover 16 | self.icon = icon # character drawn on screen 17 | self.seen_color = seen_color 18 | self.unseen_color = unseen_color 19 | self.blocks = blocks 20 | self.blocks_sight = blocks_sight 21 | 22 | terrain_types = [ 23 | Terrain('wall', None, None, libtcod.Color(130, 110, 50), 24 | libtcod.Color(0, 0, 100), True, True), 25 | Terrain('ground', None, None, libtcod.Color(200, 180, 50), 26 | libtcod.Color(50, 50, 150), False, False) 27 | ] 28 | 29 | 30 | class Map(object): 31 | """ 32 | A (width x height) region of tiles, presumably densely occupied. 33 | Has a dungeon_level and a collection of (rectangular) rooms. 34 | Has portals connecting to other maps. 35 | """ 36 | def __init__(self, height, width, dungeon_level): 37 | self.height = height 38 | self.width = width 39 | self.dungeon_level = dungeon_level 40 | self.objects = [] 41 | self.rooms = [] 42 | self.portals = [] 43 | 44 | self.random_seed = None 45 | self.rng = None 46 | 47 | self.fov_map = None 48 | 49 | # Maps default to walls (blocked) & unexplored 50 | self.terrain = [[0 for y in range(height)] for x in range(width)] 51 | self._explored = [[False for y in range(height)] for x in range(width)] 52 | 53 | def initialize_fov(self): 54 | """ 55 | Set up corresponding C state for libtcod. 56 | Must be called explicitly after loading from savegame or entering from 57 | another map. 58 | """ 59 | self.fov_needs_recompute = True 60 | self.fov_map = libtcod.map_new(self.width, self.height) 61 | for y in range(self.height): 62 | for x in range(self.width): 63 | libtcod.map_set_properties( 64 | self.fov_map, x, y, 65 | not terrain_types[self.terrain[x][y]].blocks_sight, 66 | not terrain_types[self.terrain[x][y]].blocks) 67 | 68 | def terrain_at(self, pos): 69 | """ 70 | Returns the Terrain at (pos). 71 | position *must* be within the current map. 72 | """ 73 | return terrain_types[self.terrain[pos.x][pos.y]] 74 | 75 | def is_blocked_at(self, pos): 76 | """ 77 | Returns true if impassible map terrain or any blocking objects 78 | are at (x, y). 79 | """ 80 | if terrain_types[self.terrain[pos.x][pos.y]].blocks: 81 | return True 82 | for object in self.objects: 83 | if object.blocks and object.pos == pos: 84 | return True 85 | return False 86 | 87 | def is_explored(self, pos): 88 | return self._explored[pos.x][pos.y] 89 | 90 | def explore(self, pos): 91 | self._explored[pos.x][pos.y] = True 92 | 93 | def out_of_bounds(self, pos): 94 | return "You can't go that way!" 95 | -------------------------------------------------------------------------------- /algebra.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | class Rect(object): 5 | """ 6 | A rectangle on the map. used to characterize a room. 7 | """ 8 | def __init__(self, x, y, w, h): 9 | self.x1 = x 10 | self.y1 = y 11 | self.x2 = x + max(0, w) 12 | self.y2 = y + max(0, h) 13 | 14 | def __eq__(self, other): 15 | return (self.x1 == other.x1 and 16 | self.x2 == other.x2 and 17 | self.y1 == other.y1 and 18 | self.y2 == other.y2) 19 | 20 | def center(self): 21 | return Location((self.x1 + self.x2) / 2, 22 | (self.y1 + self.y2) / 2) 23 | 24 | def intersect(self, other): 25 | """ 26 | Returns true if two rectangles intersect. 27 | """ 28 | return (self.x1 <= other.x2 and self.x2 >= other.x1 and 29 | self.y1 <= other.y2 and self.y2 >= other.y1) 30 | 31 | def contains(self, location): 32 | return (location.x > self.x1 and location.x <= self.x2 and 33 | location.y > self.y1 and location.y <= self.y2) 34 | 35 | 36 | class Location(object): 37 | def __init__(self, x, y): 38 | self.x = x 39 | self.y = y 40 | 41 | def __eq__(self, other): 42 | return (isinstance(other, self.__class__) and 43 | self.x == other.x and self.y == other.y) 44 | 45 | def __ne__(self, other): 46 | return not self.__eq__(other) 47 | 48 | def __add__(self, other): 49 | return Location(self.x + other.x, self.y + other.y) 50 | 51 | def __sub__(self, other): 52 | return Location(self.x - other.x, self.y - other.y) 53 | 54 | def set(self, x, y): 55 | self.x = x 56 | self.y = y 57 | 58 | def bound(self, rect): 59 | if (self.x > rect.x2): 60 | self.x = rect.x2 61 | if (self.y > rect.y2): 62 | self.y = rect.y2 63 | if (self.x < rect.x1): 64 | self.x = rect.x1 65 | if (self.y < rect.y1): 66 | self.y = rect.y1 67 | 68 | def to_string(self): 69 | return str(self.x) + ', ' + str(self.y) 70 | 71 | 72 | class Direction(object): 73 | def __init__(self, x, y, left=None, right=None): 74 | self.x = x 75 | self.y = y 76 | self.left = left 77 | self.right = right 78 | 79 | def __eq__(self, other): 80 | return (isinstance(other, self.__class__) and 81 | self.x == other.x and self.y == other.y) 82 | 83 | def __ne__(self, other): 84 | return not self.__eq__(other) 85 | 86 | def normalize(self): 87 | """ 88 | Normalize to length 1 (preserving direction), then round and 89 | convert to integer so the movement is restricted to the map grid. 90 | If length is 0, remains 0. 91 | """ 92 | distance = math.sqrt(self.x ** 2 + self.y ** 2) 93 | if distance > 0: 94 | self.x = int(round(self.x / distance)) 95 | self.y = int(round(self.y / distance)) 96 | 97 | 98 | north = Direction(0, -1) 99 | south = Direction(0, 1) 100 | west = Direction(-1, 0) 101 | east = Direction(1, 0) 102 | northwest = Direction(-1, -1) 103 | northeast = Direction(1, -1) 104 | southwest = Direction(-1, 1) 105 | southeast = Direction(1, 1) 106 | 107 | north.left = northwest 108 | northwest.left = west 109 | west.left = southwest 110 | southwest.left = south 111 | south.left = southeast 112 | southeast.left = east 113 | east.left = northeast 114 | northeast.left = north 115 | 116 | north.right = northeast 117 | northeast.right = east 118 | east.right = southeast 119 | southeast.right = south 120 | south.right = southwest 121 | southwest.right = west 122 | west.right = northwest 123 | northwest.right = north 124 | 125 | directions = [north, northeast, east, southeast, 126 | south, southwest, west, northwest] 127 | -------------------------------------------------------------------------------- /spells.py: -------------------------------------------------------------------------------- 1 | """ 2 | Spells (magic item effects) and targeting utility functions. 3 | 4 | Could be folded into actions.py. 5 | """ 6 | import libtcodpy as libtcod 7 | 8 | import log 9 | from components import * 10 | import actions 11 | import ai 12 | import interface 13 | 14 | HEAL_AMOUNT = 40 15 | LIGHTNING_DAMAGE = 40 16 | LIGHTNING_RANGE = 5 17 | CONFUSE_RANGE = 8 18 | FIREBALL_RADIUS = 3 19 | FIREBALL_DAMAGE = 25 20 | 21 | 22 | def _target_monster(actor, max_range=None): 23 | """ 24 | Returns a clicked monster inside FOV up to a range, 25 | or None if right-clicked. 26 | """ 27 | while True: 28 | pos = interface.target_tile(actor, max_range) 29 | if pos is None: 30 | return None 31 | 32 | for obj in actor.current_map.objects: 33 | if (obj.x == pos.x and obj.y == pos.y and obj.fighter and 34 | obj != actor): 35 | return obj 36 | 37 | 38 | def _closest_monster(actor, max_range): 39 | """ 40 | Find closest enemy in the player's FOV, up to a maximum range. 41 | """ 42 | closest_enemy = None 43 | closest_dist = max_range + 1 44 | 45 | for object in actor.current_map.objects: 46 | if (object.fighter and not object == actor and 47 | libtcod.map_is_in_fov(actor.current_map.fov_map, 48 | object.x, object.y)): 49 | dist = actor.distance_to(object) 50 | if dist < closest_dist: 51 | closest_enemy = object 52 | closest_dist = dist 53 | return closest_enemy 54 | 55 | 56 | def cast_heal(actor): 57 | """ 58 | Heal the caster. 59 | """ 60 | if actor.fighter.hp == actor.fighter.max_hp: 61 | log.message('You are already at full health.', libtcod.red) 62 | return 'cancelled' 63 | 64 | log.message('Your wounds start to feel better!', libtcod.light_violet) 65 | actions.heal(actor.fighter, HEAL_AMOUNT) 66 | 67 | 68 | def cast_lightning(actor): 69 | """ 70 | Find closest enemy (inside a maximum range) and damage it. 71 | """ 72 | monster = _closest_monster(actor, LIGHTNING_RANGE) 73 | if monster is None: 74 | log.message('No enemy is close enough to strike.', libtcod.red) 75 | return 'cancelled' 76 | 77 | log.message('A lighting bolt strikes the ' + monster.name + 78 | ' with a loud thunder! The damage is ' + 79 | str(LIGHTNING_DAMAGE) + ' hit points.', libtcod.light_blue) 80 | actions.inflict_damage(actor, monster.fighter, LIGHTNING_DAMAGE) 81 | 82 | 83 | def cast_fireball(actor): 84 | log.message('Left-click a target tile for the fireball, ' 85 | 'or right-click to cancel.', libtcod.light_cyan) 86 | pos = interface.target_tile(actor) 87 | if pos is None: 88 | return 'cancelled' 89 | log.message('The fireball explodes, burning everything within ' + 90 | str(FIREBALL_RADIUS) + ' tiles!', libtcod.orange) 91 | 92 | for obj in actor.current_map.objects: 93 | if obj.distance(pos) <= FIREBALL_RADIUS and obj.fighter: 94 | log.message('The ' + obj.name + ' gets burned for ' + 95 | str(FIREBALL_DAMAGE) + ' hit points.', 96 | libtcod.orange) 97 | actions.inflict_damage(actor, obj.fighter, FIREBALL_DAMAGE) 98 | 99 | 100 | def cast_confuse(actor): 101 | log.message('Left-click an enemy to confuse it, or right-click to cancel.', 102 | libtcod.light_cyan) 103 | monster = _target_monster(actor, CONFUSE_RANGE) 104 | if monster is None: 105 | return 'cancelled' 106 | 107 | old_ai = monster.ai 108 | monster.ai = AI(ai.confused_monster, ai.confused_monster_metadata(old_ai)) 109 | monster.ai.set_owner(monster) 110 | log.message('The eyes of the ' + monster.name + 111 | ' look vacant, as he starts to stumble around!', 112 | libtcod.light_green) 113 | -------------------------------------------------------------------------------- /components.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple entity system: any renderable Object can have 3 | a number of Components attached. 4 | """ 5 | import math 6 | import algebra 7 | 8 | 9 | class Object: 10 | """ 11 | This is a generic object: the player, a monster, an item, the stairs... 12 | It's always represented by a character on screen. 13 | """ 14 | def __init__(self, pos, char, name, color, 15 | blocks=False, always_visible=False, 16 | fighter=None, ai=None, item=None, equipment=None): 17 | self.pos = pos 18 | self.char = char 19 | self.name = name 20 | self.color = color 21 | self.blocks = blocks 22 | self.always_visible = always_visible 23 | 24 | self.fighter = fighter 25 | self._ensure_ownership(fighter) 26 | self.ai = ai 27 | self._ensure_ownership(ai) 28 | self.item = item 29 | self._ensure_ownership(item) 30 | self.equipment = equipment 31 | self._ensure_ownership(equipment) 32 | 33 | @property 34 | def x(self): 35 | return self.pos.x 36 | 37 | @property 38 | def y(self): 39 | return self.pos.y 40 | 41 | def _ensure_ownership(self, component): 42 | if (component): 43 | component.set_owner(self) 44 | 45 | def distance_to(self, other): 46 | """ 47 | Return the distance to another object. 48 | """ 49 | dx = other.x - self.x 50 | dy = other.y - self.y 51 | return math.sqrt(dx ** 2 + dy ** 2) 52 | 53 | def distance(self, x, y): 54 | """ 55 | Return the distance to some coordinates. 56 | """ 57 | return math.sqrt((x - self.x) ** 2 + (y - self.y) ** 2) 58 | 59 | def distance(self, pos): 60 | """ 61 | Return the distance to some coordinates. 62 | """ 63 | return math.sqrt((pos.x - self.x) ** 2 + (pos.y - self.y) ** 2) 64 | 65 | 66 | class Component: 67 | """ 68 | Base class for components to minimize boilerplate. 69 | """ 70 | def set_owner(self, entity): 71 | self.owner = entity 72 | 73 | 74 | class Fighter(Component): 75 | """ 76 | Combat-related properties and methods (monster, player, NPC). 77 | """ 78 | def __init__(self, hp, defense, power, xp, death_function=None): 79 | self.base_max_hp = hp 80 | self.hp = hp 81 | self.base_defense = defense 82 | self.base_power = power 83 | self.xp = xp 84 | self.death_function = death_function 85 | 86 | @property 87 | def power(self): 88 | bonus = sum(equipment.power_bonus for equipment 89 | in _get_all_equipped(self.owner)) 90 | return self.base_power + bonus 91 | 92 | @property 93 | def defense(self): 94 | bonus = sum(equipment.defense_bonus for equipment 95 | in _get_all_equipped(self.owner)) 96 | return self.base_defense + bonus 97 | 98 | @property 99 | def max_hp(self): 100 | bonus = sum(equipment.max_hp_bonus for equipment 101 | in _get_all_equipped(self.owner)) 102 | return self.base_max_hp + bonus 103 | 104 | 105 | class Item(Component): 106 | """ 107 | An item that can be picked up and used. 108 | """ 109 | def __init__(self, description=None, count=1, use_function=None): 110 | self.description = description 111 | self.use_function = use_function 112 | self.count = count 113 | 114 | def can_combine(self, other): 115 | """ 116 | Returns true if other can stack with self. 117 | Terribly simple for now. 118 | """ 119 | return other.item and other.name == self.owner.name 120 | 121 | 122 | class Equipment(Component): 123 | """ 124 | An object that can be equipped, yielding bonuses. 125 | Requires an Item component. 126 | """ 127 | def __init__(self, slot, power_bonus=0, defense_bonus=0, max_hp_bonus=0): 128 | self.power_bonus = power_bonus 129 | self.defense_bonus = defense_bonus 130 | self.max_hp_bonus = max_hp_bonus 131 | 132 | self.slot = slot 133 | self.is_equipped = False 134 | 135 | def set_owner(self, entity): 136 | Component.set_owner(self, entity) 137 | 138 | # There must be an Item component for the Equipment 139 | # component to work properly. 140 | if entity.item is None: 141 | entity.item = Item() 142 | entity.item.set_owner(entity) 143 | 144 | 145 | class AI(Component): 146 | def __init__(self, take_turn, metadata=None): 147 | self._turn_function = take_turn 148 | self._metadata = metadata 149 | 150 | def take_turn(self, player): 151 | self._turn_function(self.owner, player, self._metadata) 152 | 153 | 154 | def _get_all_equipped(obj): 155 | """ 156 | Returns a list of all equipped items. 157 | """ 158 | if hasattr(obj, 'inventory'): 159 | equipped_list = [] 160 | for item in obj.inventory: 161 | if item.equipment and item.equipment.is_equipped: 162 | equipped_list.append(item.equipment) 163 | return equipped_list 164 | else: 165 | return [] 166 | -------------------------------------------------------------------------------- /interface.py: -------------------------------------------------------------------------------- 1 | """ 2 | All functions that take input (other than the game mainloop). 3 | 4 | interface.poll() returns (libtcod.Key, libtcod.Mouse) with key *presses* 5 | and mouse events, but not key *releases*. 6 | interface.parse_move(key) translates a libtcod.Key into directional movement. 7 | interface.log_display(width=60) 8 | interface.target_tile(actor, max_range=None) 9 | """ 10 | import libtcodpy as libtcod 11 | 12 | import config 13 | import algebra 14 | import log 15 | import renderer 16 | 17 | 18 | def poll(): 19 | key = libtcod.Key() 20 | mouse = libtcod.Mouse() 21 | libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS | 22 | libtcod.EVENT_MOUSE, key, mouse) 23 | return (key, mouse) 24 | 25 | 26 | def debounce(): 27 | key = libtcod.Key() 28 | mouse = libtcod.Mouse() 29 | # Can't pass None for mouse even if we are only waiting for a key release. 30 | libtcod.sys_wait_for_event(libtcod.EVENT_KEY_RELEASE, key, mouse, True) 31 | 32 | 33 | def parse_move(key): 34 | """ 35 | Returns (bool, direction, bool). 36 | First value is True if a direction key was pressed, False otherwise. 37 | Direction will be None if first value is False or the '.' or numpad 5 were pressed. 38 | Last value is True if shift was held (run / page-scroll), False otherwise. 39 | """ 40 | key_char = chr(key.c) 41 | if (key.vk == libtcod.KEY_UP or key.vk == libtcod.KEY_KP8 or 42 | key_char == 'k' or key_char == 'K'): 43 | return (True, algebra.north, key.shift) 44 | elif (key.vk == libtcod.KEY_DOWN or key.vk == libtcod.KEY_KP2 or 45 | key_char == 'j' or key_char == 'J'): 46 | return (True, algebra.south, key.shift) 47 | elif (key.vk == libtcod.KEY_LEFT or key.vk == libtcod.KEY_KP4 or 48 | key_char == 'h' or key_char == 'H'): 49 | return (True, algebra.west, key.shift) 50 | elif (key.vk == libtcod.KEY_RIGHT or key.vk == libtcod.KEY_KP6 or 51 | key_char == 'l' or key_char == 'L'): 52 | return (True, algebra.east, key.shift) 53 | elif (key.vk == libtcod.KEY_HOME or key.vk == libtcod.KEY_KP7 or 54 | key_char == 'y' or key_char == 'Y'): 55 | return (True, algebra.northwest, key.shift) 56 | elif (key.vk == libtcod.KEY_PAGEUP or key.vk == libtcod.KEY_KP9 or 57 | key_char == 'u' or key_char == 'U'): 58 | return (True, algebra.northeast, key.shift) 59 | elif (key.vk == libtcod.KEY_END or key.vk == libtcod.KEY_KP1 or 60 | key_char == 'b' or key_char == 'B'): 61 | return (True, algebra.southwest, key.shift) 62 | elif (key.vk == libtcod.KEY_PAGEDOWN or key.vk == libtcod.KEY_KP3 or 63 | key_char == 'n' or key_char == 'N'): 64 | return (True, algebra.southeast, key.shift) 65 | elif (key.vk == libtcod.KEY_KP5 or key_char == '.'): 66 | # do nothing but note that a relevant key was pressed 67 | return (True, None, False) 68 | return (False, None, False) 69 | 70 | 71 | def _colored_text_list(lines, width): 72 | """ 73 | Display *lines* of (text, color) in a window of size *width*. 74 | Scroll through them if the mouse wheel is spun or the arrows are pressed. 75 | """ 76 | length = len(lines) 77 | height = min(length, 40) 78 | window = libtcod.console_new(width, height) 79 | offset = -height 80 | 81 | while True: 82 | if offset > -height: 83 | offset = -height 84 | if offset < -length: 85 | offset = -length 86 | 87 | libtcod.console_clear(window) 88 | renderer.write_log(lines[offset:length + offset + height], 89 | window, 0, 0) 90 | 91 | x = config.SCREEN_WIDTH/2 - width/2 92 | y = config.SCREEN_HEIGHT/2 - height/2 93 | libtcod.console_blit(window, 0, 0, width, height, 0, x, y, 1.0, 0.7) 94 | 95 | libtcod.console_flush() 96 | while True: 97 | (key, mouse) = poll() 98 | (key_pressed, direction, shift) = parse_move(key) 99 | if key_pressed: 100 | if direction == algebra.north and not shift: 101 | offset -= 1 102 | break 103 | elif direction == algebra.south and not shift: 104 | offset += 1 105 | break 106 | elif (direction == algebra.northeast or 107 | (direction == algebra.north and shift)): 108 | offset -= height 109 | break 110 | elif (direction == algebra.southeast or 111 | (direction == algebra.south and shift)): 112 | offset += height 113 | break 114 | elif (key.vk == libtcod.KEY_ALT or 115 | key.vk == libtcod.KEY_CONTROL or 116 | key.vk == libtcod.KEY_SHIFT or 117 | key.vk == libtcod.KEY_NONE): 118 | break 119 | return 120 | 121 | 122 | def log_display(width=60): 123 | """ 124 | Display the recent log history, wait for any keypress. 125 | """ 126 | _colored_text_list(log.game_msgs, width) 127 | 128 | 129 | def target_tile(actor, max_range=None): 130 | """ 131 | Return the position of a tile left-clicked in player's FOV 132 | (optionally in a range), or (None,None) if right-clicked. 133 | """ 134 | (key, mouse) = poll() 135 | (ox, oy) = (mouse.cx, mouse.cy) 136 | using_mouse = False 137 | using_keyboard = False 138 | (kx, ky) = renderer.ScreenCoords.fromWorldCoords(actor.camera_position, 139 | actor.pos) 140 | pos = None 141 | 142 | while True: 143 | # Render the screen. This erases the inventory and shows 144 | # the names of objects under the mouse. 145 | libtcod.console_flush() 146 | (key, mouse) = poll() 147 | renderer.render_all(actor, (kx, ky)) 148 | actor.current_map.fov_needs_recompute = False 149 | if (mouse.cx != ox or mouse.cy != oy): 150 | using_mouse = True 151 | using_keyboard = False 152 | (key_pressed, direction, shift) = parse_move(key) 153 | if key_pressed: 154 | using_keyboard = True 155 | if using_mouse: 156 | (ox, oy) = (mouse.cx, mouse.cy) 157 | using_mouse = False 158 | if direction: 159 | kx += direction.x 160 | ky += direction.y 161 | 162 | if using_mouse: 163 | (kx, ky) = (mouse.cx, mouse.cy) 164 | pos = renderer.ScreenCoords.toWorldCoords(actor.camera_position, (kx, ky)) 165 | libtcod.console_set_default_background(renderer._overlay, libtcod.black) 166 | libtcod.console_clear(renderer._overlay) 167 | (ux, uy) = renderer.ScreenCoords.fromWorldCoords(actor.camera_position, 168 | actor.pos) 169 | libtcod.line_init(ux, uy, kx, ky) 170 | 171 | nx, ny = libtcod.line_step() 172 | while ((not (nx is None)) and nx >= 0 and ny >= 0 and 173 | nx < config.MAP_PANEL_WIDTH and 174 | ny < config.MAP_PANEL_HEIGHT): 175 | libtcod.console_set_char_background(renderer._overlay, nx, ny, libtcod.sepia, libtcod.BKGND_SET) 176 | nx, ny = libtcod.line_step() 177 | 178 | if mouse.rbutton_pressed or key.vk == libtcod.KEY_ESCAPE: 179 | libtcod.console_clear(renderer._overlay) 180 | return None 181 | 182 | # Accept the target if the player clicked in FOV 183 | # and within the range specified. 184 | if ((mouse.lbutton_pressed or key.vk == libtcod.KEY_ENTER) and 185 | libtcod.map_is_in_fov(actor.current_map.fov_map, pos.x, pos.y) and 186 | (max_range is None or actor.distance(pos) <= max_range)): 187 | libtcod.console_clear(renderer._overlay) 188 | return pos 189 | -------------------------------------------------------------------------------- /cartographer.py: -------------------------------------------------------------------------------- 1 | import libtcodpy as libtcod 2 | 3 | import config 4 | import algebra 5 | import map 6 | from components import * 7 | import ai 8 | import spells 9 | import miscellany 10 | 11 | ROOM_MAX_SIZE = 10 12 | ROOM_MIN_SIZE = 6 13 | MAX_ROOMS = 30 14 | 15 | 16 | def _random_position_in_room(room): 17 | return algebra.Location(libtcod.random_get_int(0, room.x1+1, room.x2-1), 18 | libtcod.random_get_int(0, room.y1+1, room.y2-1)) 19 | 20 | 21 | def _create_room(new_map, room): 22 | """ 23 | Make the tiles in a rectangle passable 24 | """ 25 | for x in range(room.x1 + 1, room.x2): 26 | for y in range(room.y1 + 1, room.y2): 27 | new_map.terrain[x][y] = 1 28 | 29 | 30 | def _create_h_tunnel(new_map, x1, x2, y): 31 | for x in range(min(x1, x2), max(x1, x2) + 1): 32 | new_map.terrain[x][y] = 1 33 | 34 | 35 | def _create_v_tunnel(new_map, y1, y2, x): 36 | for y in range(min(y1, y2), max(y1, y2) + 1): 37 | new_map.terrain[x][y] = 1 38 | 39 | 40 | def _random_choice_index(chances): 41 | """ 42 | choose one option from list of chances, returning its index 43 | """ 44 | dice = libtcod.random_get_int(0, 1, sum(chances)) 45 | 46 | running_sum = 0 47 | choice = 0 48 | for w in chances: 49 | running_sum += w 50 | 51 | if dice <= running_sum: 52 | return choice 53 | choice += 1 54 | 55 | 56 | def _random_choice(chances_dict): 57 | """ 58 | choose one option from dictionary of chances, returning its key 59 | """ 60 | chances = chances_dict.values() 61 | strings = chances_dict.keys() 62 | 63 | return strings[_random_choice_index(chances)] 64 | 65 | 66 | def _from_dungeon_level(new_map, table): 67 | # Returns a value that depends on level. 68 | # The table specifies what value occurs after each level, default is 0. 69 | for (value, level) in reversed(table): 70 | if new_map.dungeon_level >= level: 71 | return value 72 | return 0 73 | 74 | 75 | loot_values = { 76 | 'heal' : miscellany.healing_potion, 77 | 'lightning' : miscellany.lightning_scroll, 78 | 'fireball' : miscellany.fireball_scroll, 79 | 'confuse' : miscellany.confusion_scroll, 80 | 'sword' : miscellany.sword, 81 | 'shield' : miscellany.shield 82 | } 83 | 84 | def _place_objects(new_map, room, player): 85 | max_monsters = _from_dungeon_level(new_map, [[2, 1], [3, 4], [5, 6]]) 86 | 87 | monster_chances = {} 88 | # orc always shows up, even if all other monsters have 0 chance. 89 | monster_chances['orc'] = 80 90 | monster_chances['troll'] = _from_dungeon_level(new_map, [[15, 3], [30, 5], [60, 7]]) 91 | 92 | max_items = _from_dungeon_level(new_map, [[1, 1], [2, 4]]) 93 | 94 | item_chances = {} 95 | # Healing potion always shows up, even if all other items have 0 chance. 96 | item_chances['heal'] = 35 97 | item_chances['lightning'] = _from_dungeon_level(new_map, [[25, 4]]) 98 | item_chances['fireball'] = _from_dungeon_level(new_map, [[25, 6]]) 99 | item_chances['confuse'] = _from_dungeon_level(new_map, [[10, 2]]) 100 | item_chances['sword'] = _from_dungeon_level(new_map, [[5, 4]]) 101 | item_chances['shield'] = _from_dungeon_level(new_map, [[15, 8]]) 102 | 103 | num_monsters = libtcod.random_get_int(0, 0, max_monsters) 104 | for i in range(num_monsters): 105 | pos = _random_position_in_room(room) 106 | 107 | if not new_map.is_blocked_at(pos): 108 | choice = _random_choice(monster_chances) 109 | if choice == 'orc': 110 | fighter_component = Fighter(hp=20, defense=0, power=4, xp=35, death_function=ai.monster_death) 111 | ai_component = AI(ai.basic_monster, ai.basic_monster_metadata(player)) 112 | monster = Object(pos, 'o', 'orc', libtcod.desaturated_green, 113 | blocks=True, fighter=fighter_component, ai=ai_component) 114 | 115 | elif choice == 'troll': 116 | fighter_component = Fighter(hp=30, defense=2, power=8, xp=100, death_function=ai.monster_death) 117 | ai_component = AI(ai.basic_monster, ai.basic_monster_metadata(player)) 118 | monster = Object(pos, 'T', 'troll', libtcod.darker_green, 119 | blocks=True, fighter=fighter_component, ai=ai_component) 120 | 121 | new_map.objects.append(monster) 122 | monster.current_map = new_map 123 | 124 | num_items = libtcod.random_get_int(0, 0, max_items) 125 | for i in range(num_items): 126 | pos = _random_position_in_room(room) 127 | 128 | if not new_map.is_blocked_at(pos): 129 | choice = _random_choice(item_chances) 130 | item = loot_values[choice](pos=pos) 131 | 132 | new_map.objects.insert(0, item) 133 | item.always_visible = True # Items are visible even out-of-FOV, if in an explored area 134 | 135 | 136 | def _build_map(new_map): 137 | new_map.rng = libtcod.random_new_from_seed(new_map.random_seed) 138 | num_rooms = 0 139 | for r in range(MAX_ROOMS): 140 | w = libtcod.random_get_int(new_map.rng, ROOM_MIN_SIZE, ROOM_MAX_SIZE) 141 | h = libtcod.random_get_int(new_map.rng, ROOM_MIN_SIZE, ROOM_MAX_SIZE) 142 | x = libtcod.random_get_int(new_map.rng, 0, new_map.width - w - 1) 143 | y = libtcod.random_get_int(new_map.rng, 0, new_map.height - h - 1) 144 | 145 | new_room = map.Room(x, y, w, h) 146 | 147 | failed = False 148 | for other_room in new_map.rooms: 149 | if new_room.intersect(other_room): 150 | failed = True 151 | break 152 | 153 | if not failed: 154 | # There are no intersections, so this room is valid. 155 | _create_room(new_map, new_room) 156 | new_ctr = new_room.center() 157 | 158 | if num_rooms > 0: 159 | prev_ctr = new_map.rooms[num_rooms-1].center() 160 | 161 | if libtcod.random_get_int(new_map.rng, 0, 1) == 1: 162 | _create_h_tunnel(new_map, prev_ctr.x, new_ctr.x, prev_ctr.y) 163 | _create_v_tunnel(new_map, prev_ctr.y, new_ctr.y, new_ctr.x) 164 | else: 165 | _create_v_tunnel(new_map, prev_ctr.y, new_ctr.y, prev_ctr.x) 166 | _create_h_tunnel(new_map, prev_ctr.x, new_ctr.x, new_ctr.y) 167 | 168 | new_map.rooms.append(new_room) 169 | num_rooms += 1 170 | 171 | # Create stairs at the center of the last room. 172 | stairs = Object(new_ctr, '<', 'stairs down', libtcod.white, always_visible=True) 173 | stairs.destination = None 174 | stairs.dest_position = None 175 | new_map.objects.insert(0, stairs) 176 | new_map.portals.insert(0, stairs) 177 | 178 | # Test - tunnel off the right edge 179 | # _create_h_tunnel(new_map, new_ctr.x, new_map.width-1, new_ctr.y) 180 | 181 | 182 | def make_map(player, dungeon_level): 183 | """ 184 | Creates a new simple map at the given dungeon level. 185 | Sets player.current_map to the new map, and adds the player as the first 186 | object. 187 | """ 188 | new_map = map.Map(config.MAP_HEIGHT, config.MAP_WIDTH, dungeon_level) 189 | new_map.objects.append(player) 190 | player.current_map = new_map 191 | player.camera_position = algebra.Location(0, 0) 192 | new_map.random_seed = libtcod.random_save(0) 193 | _build_map(new_map) 194 | for new_room in new_map.rooms: 195 | _place_objects(new_map, new_room, player) 196 | player.pos = new_map.rooms[0].center() 197 | 198 | new_map.initialize_fov() 199 | return new_map 200 | 201 | 202 | def _test_map_repeatability(): 203 | """ 204 | Require that two calls to _build_map() with the same seed produce the 205 | same corridors and rooms. 206 | """ 207 | map1 = map.Map(config.MAP_HEIGHT, config.MAP_WIDTH, 3) 208 | map1.random_seed = libtcod.random_save(0) 209 | _build_map(map1) 210 | 211 | map2 = map.Map(config.MAP_HEIGHT, config.MAP_WIDTH, 3) 212 | map2.random_seed = map1.random_seed 213 | _build_map(map2) 214 | 215 | assert map1.terrain == map2.terrain 216 | for i in range(len(map1.rooms)): 217 | assert map1.rooms[i] == map2.rooms[i] 218 | 219 | if __name__ == '__main__': 220 | _test_map_repeatability() 221 | print('Cartographer tests complete.') 222 | -------------------------------------------------------------------------------- /actions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of actions. 3 | 4 | Includes those which might be used by the AI (movement and combat) 5 | and those which are currently only offered to the player. 6 | Magical effects and targeting (spells.py) could also live here. 7 | 8 | Conditionals and interfaces for the player sit up top in roguelike.py. 9 | """ 10 | import libtcodpy as libtcod 11 | import copy 12 | 13 | import log 14 | import algebra 15 | from components import * 16 | 17 | 18 | def move(o, direction): 19 | """ 20 | Moves object by (dx, dy). 21 | Returns true if move succeeded. 22 | """ 23 | goal = o.pos + direction 24 | if not o.current_map.is_blocked_at(goal): 25 | o.pos = goal 26 | return True 27 | return False 28 | 29 | 30 | def move_towards(o, target_pos): 31 | """ 32 | Moves object one step towards target location. 33 | Returns true if move succeeded. 34 | """ 35 | dir = algebra.Direction(target_pos.x - o.x, target_pos.y - o.y) 36 | dir.normalize() 37 | return move(o, dir) 38 | 39 | 40 | def attack(fighter, target, report=True): 41 | """ 42 | A simple formula for attack damage. 43 | """ 44 | damage = fighter.power - target.fighter.defense 45 | 46 | if damage > 0: 47 | if report: 48 | log.message( 49 | fighter.owner.name.capitalize() + ' attacks ' + target.name + 50 | ' for ' + str(damage) + ' hit points.') 51 | inflict_damage(fighter.owner, target.fighter, damage) 52 | elif report: 53 | log.message( 54 | fighter.owner.name.capitalize() + ' attacks ' + target.name + 55 | ' but it has no effect!') 56 | 57 | 58 | def inflict_damage(actor, fighter, damage): 59 | """ 60 | Apply damage. 61 | """ 62 | if damage > 0: 63 | fighter.hp -= damage 64 | 65 | if fighter.hp <= 0: 66 | function = fighter.death_function 67 | if function is not None: 68 | function(fighter.owner) 69 | 70 | actor.fighter.xp += fighter.xp 71 | 72 | 73 | def heal(fighter, amount): 74 | """ 75 | Heal by the given amount, without going over the maximum. 76 | """ 77 | fighter.hp += amount 78 | if fighter.hp > fighter.max_hp: 79 | fighter.hp = fighter.max_hp 80 | 81 | 82 | def pick_up(actor, o, report=True): 83 | """ 84 | Add an Object to the actor's inventory and remove from the map. 85 | """ 86 | for p in actor.inventory: 87 | if o.item.can_combine(p): 88 | p.item.count += o.item.count 89 | actor.current_map.objects.remove(o) 90 | if report: 91 | log.message(actor.name.capitalize() + ' picked up a ' + o.name + '!', libtcod.green) 92 | return True 93 | 94 | if len(actor.inventory) >= 22: 95 | if report: 96 | log.message(actor.name.capitalize() + ' inventory is full, cannot pick up ' + 97 | o.name + '.', libtcod.red) 98 | return False 99 | else: 100 | actor.inventory.append(o) 101 | actor.current_map.objects.remove(o) 102 | if report: 103 | log.message(actor.name.capitalize() + ' picked up a ' + o.name + '!', libtcod.green) 104 | 105 | # Special case: automatically equip if the corresponding equipment slot is unused. 106 | equipment = o.equipment 107 | if equipment and _get_equipped_in_slot(actor, equipment.slot) is None: 108 | equip(actor, equipment) 109 | return True 110 | 111 | 112 | def drop(actor, o, report=True): 113 | """ 114 | Remove an Object from the actor's inventory and add it to the map 115 | at the player's coordinates. 116 | If it's equipment, dequip before dropping. 117 | """ 118 | must_split = False 119 | if o.item.count > 1: 120 | o.item.count -= 1 121 | must_split = True 122 | else: 123 | if o.equipment: 124 | dequip(actor, o.equipment, True) 125 | actor.inventory.remove(o) 126 | 127 | combined = False 128 | for p in actor.current_map.objects: 129 | if p.pos == actor.pos and o.item.can_combine(p): 130 | p.item.count += 1 131 | combined = True 132 | break 133 | 134 | if not combined: 135 | new_o = o 136 | if must_split: 137 | new_o = copy.deepcopy(o) 138 | new_o.item.count = 1 139 | new_o.pos = actor.pos 140 | actor.current_map.objects.append(new_o) 141 | 142 | if report: 143 | log.message(actor.name.capitalize() + ' dropped a ' + o.name + '.', libtcod.yellow) 144 | 145 | 146 | def use(actor, o, report=True): 147 | """ 148 | If the object has the Equipment component, toggle equip/dequip. 149 | Otherwise invoke its use_function and (if not cancelled) destroy it. 150 | """ 151 | if o.equipment: 152 | _toggle_equip(actor, o.equipment, report) 153 | return 154 | 155 | if o.item.use_function is None: 156 | if report: 157 | log.message('The ' + o.name + ' cannot be used.') 158 | else: 159 | if o.item.use_function(actor) != 'cancelled': 160 | if o.item.count > 1: 161 | o.item.count -= 1 162 | else: 163 | actor.inventory.remove(o) 164 | 165 | 166 | def _toggle_equip(actor, eqp, report=True): 167 | if eqp.is_equipped: 168 | dequip(actor, eqp, report) 169 | else: 170 | equip(actor, eqp, report) 171 | 172 | 173 | def equip(actor, eqp, report=True): 174 | """ 175 | Equip the object (and log unless report=False). 176 | Ensure only one object per slot. 177 | """ 178 | old_equipment = _get_equipped_in_slot(actor, eqp.slot) 179 | if old_equipment is not None: 180 | dequip(actor, old_equipment, report) 181 | 182 | eqp.is_equipped = True 183 | if report: 184 | log.message('Equipped ' + eqp.owner.name + ' on ' + eqp.slot + '.', libtcod.light_green) 185 | 186 | 187 | def dequip(actor, eqp, report=True): 188 | """ 189 | Dequip the object (and log). 190 | """ 191 | if not eqp.is_equipped: 192 | return 193 | eqp.is_equipped = False 194 | if report: 195 | log.message('Dequipped ' + eqp.owner.name + ' from ' + eqp.slot + '.', libtcod.light_yellow) 196 | 197 | 198 | def _get_equipped_in_slot(actor, slot): 199 | """ 200 | Returns Equipment in a slot, or None. 201 | """ 202 | if hasattr(actor, 'inventory'): 203 | for obj in actor.inventory: 204 | if obj.equipment and obj.equipment.slot == slot and obj.equipment.is_equipped: 205 | return obj.equipment 206 | return None 207 | 208 | 209 | class _MockMap(object): 210 | def is_blocked_at(self, pos): 211 | return False 212 | 213 | 214 | def _test_move(): 215 | o = Object(algebra.Location(0, 0), 'o', 'test object', libtcod.white) 216 | o.current_map = _MockMap() 217 | assert o.pos == algebra.Location(0, 0) 218 | move(o, algebra.south) 219 | assert o.pos == algebra.Location(0, 1) 220 | move(o, algebra.southeast) 221 | assert o.pos == algebra.Location(1, 2) 222 | 223 | 224 | def _test_move_towards(): 225 | o = Object(algebra.Location(0, 0), 'o', 'test object', libtcod.white) 226 | o.current_map = _MockMap() 227 | assert o.pos == algebra.Location(0, 0) 228 | move_towards(o, algebra.Location(10, 10)) 229 | assert o.pos == algebra.Location(1, 1) 230 | move_towards(o, algebra.Location(10, 10)) 231 | assert o.pos == algebra.Location(2, 2) 232 | move_towards(o, algebra.Location(-10, 2)) 233 | assert o.pos == algebra.Location(1, 2) 234 | move_towards(o, o.pos) 235 | assert o.pos == algebra.Location(1, 2) 236 | 237 | 238 | def _test_attack(): 239 | af = Fighter(100, 0, 10, 0) 240 | df = Fighter(100, 0, 0, 0) 241 | a = Object(algebra.Location(0, 0), 'a', 'test attacker', libtcod.white, fighter=af) 242 | d = Object(algebra.Location(1, 1), 'd', 'test defender', libtcod.white, fighter=df) 243 | 244 | assert af.hp == 100 245 | assert df.hp == 100 246 | # if defense == 0, full damage is done 247 | attack(af, d, False) 248 | assert df.hp == 90 249 | df.base_defense = 5 250 | attack(af, d, False) 251 | assert df.hp == 85 252 | # if defense > attack, no damage is done 253 | df.base_defense = 15 254 | attack(af, d, False) 255 | assert df.hp == 85 256 | 257 | 258 | def _test_actions(): 259 | _test_move() 260 | _test_move_towards() 261 | _test_attack() 262 | 263 | 264 | if __name__ == '__main__': 265 | _test_actions() 266 | print('Action tests complete.') 267 | -------------------------------------------------------------------------------- /roguelike.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # libtcod python tutorial 4 | # 5 | 6 | import libtcodpy as libtcod 7 | import shelve 8 | import cProfile 9 | 10 | import config 11 | import log 12 | import algebra 13 | from components import * 14 | import renderer 15 | import interface 16 | import actions 17 | import ai 18 | import miscellany 19 | import cartographer 20 | 21 | INVENTORY_WIDTH = 50 22 | CHARACTER_SCREEN_WIDTH = 30 23 | LEVEL_SCREEN_WIDTH = 40 24 | 25 | # Experience and level-ups 26 | LEVEL_UP_BASE = 200 27 | LEVEL_UP_FACTOR = 150 28 | 29 | 30 | def try_pick_up(player): 31 | for object in player.current_map.objects: 32 | if object.x == player.x and object.y == player.y and object.item: 33 | return actions.pick_up(player, object) 34 | return False 35 | 36 | 37 | def try_drop(player): 38 | chosen_item = inventory_menu( 39 | player, 40 | 'Press the key next to an item to drop it, x to examine, or any other to cancel.\n') 41 | if chosen_item is not None: 42 | actions.drop(player, chosen_item.owner) 43 | return True 44 | return False 45 | 46 | 47 | def try_use(player): 48 | chosen_item = inventory_menu( 49 | player, 50 | 'Press the key next to an item to use it, x to examine, or any other to cancel.\n') 51 | if chosen_item is not None: 52 | actions.use(player, chosen_item.owner) 53 | return True 54 | return False 55 | 56 | 57 | def player_move_or_attack(player, direction, try_running): 58 | """ 59 | Returns true if the player makes an attack or moves successfully; 60 | false if the attempt to move fails. 61 | """ 62 | goal = player.pos + direction 63 | if (goal.x < 0 or goal.y < 0 or 64 | goal.x >= player.current_map.width or 65 | goal.y >= player.current_map.height): 66 | log.message(player.current_map.out_of_bounds(goal)) 67 | return False 68 | 69 | # Is there an attackable object? 70 | target = None 71 | for object in player.current_map.objects: 72 | if object.fighter and object.pos == goal: 73 | target = object 74 | break 75 | 76 | if target is not None: 77 | actions.attack(player.fighter, target) 78 | return True 79 | else: 80 | if actions.move(player, direction): 81 | player.current_map.fov_needs_recompute = True 82 | if try_running: 83 | player.game_state = 'running' 84 | player.run_direction = direction 85 | return True 86 | 87 | return False 88 | 89 | 90 | def inventory_menu(player, header): 91 | """ 92 | Show a menu with each item of the inventory as an option. 93 | """ 94 | if len(player.inventory) == 0: 95 | renderer.menu(header, 'Inventory is empty.', INVENTORY_WIDTH) 96 | return None 97 | 98 | options = [] 99 | for obj in player.inventory: 100 | text = obj.name 101 | # Show additional information, in case it's equipped. 102 | if obj.item.count > 1: 103 | text = text + ' (x' + str(obj.item.count) + ')' 104 | if obj.equipment and obj.equipment.is_equipped: 105 | text = text + ' (on ' + obj.equipment.slot + ')' 106 | options.append(text) 107 | 108 | (char, index) = renderer.menu(header, options, INVENTORY_WIDTH) 109 | 110 | if index is not None: 111 | return player.inventory[index].item 112 | 113 | if char == ord('x'): 114 | (c2, i2) = renderer.menu('Press the key next to an item to examine it, or any other to cancel.\n', options, INVENTORY_WIDTH) 115 | if i2 is not None and player.inventory[i2].item.description is not None: 116 | # renderer.msgbox(player.inventory[i2].item.description) 117 | log.message(player.inventory[i2].item.description) 118 | 119 | return None 120 | 121 | 122 | def display_character_info(player): 123 | level_up_xp = LEVEL_UP_BASE + player.level * LEVEL_UP_FACTOR 124 | base_attack_power = player.fighter.base_power 125 | attack_power = player.fighter.power 126 | base_defense = player.fighter.base_defense 127 | defense = player.fighter.defense 128 | renderer.msgbox('Character Information\n\nLevel: ' + str(player.level) + 129 | '\nExperience: ' + str(player.fighter.xp) + 130 | '\nExperience to level up: ' + str(level_up_xp) + 131 | '\n\nMaximum HP: ' + str(player.fighter.max_hp) + 132 | '\nAttack: ' + str(attack_power) + 133 | ' (' + str(base_attack_power) + ' + ' + str(attack_power - base_attack_power) + ')' 134 | '\nDefense: ' + str(defense) + 135 | ' (' + str(base_defense) + ' + ' + str(defense - base_defense) + ')', 136 | CHARACTER_SCREEN_WIDTH) 137 | 138 | 139 | def display_help(): 140 | renderer.msgbox('numpad keys to move, or:\n' + 141 | ' h (west) j (south) k (north) l (east)\n' + 142 | ' y (nw) u (ne) b (sw) n (se) . (wait)\n' + 143 | ' shift-move to run\n' + 144 | 'g/get, d/drop, c/character information\n' + 145 | 'i/inventory, equip, or use item\n' + 146 | '= level_up_xp: 245 | player.level += 1 246 | player.fighter.xp -= level_up_xp 247 | log.message('Your battle skills grow stronger! You reached level ' + str(player.level) + '!', libtcod.yellow) 248 | 249 | choice = None 250 | while choice is None: 251 | (char, choice) = renderer.menu( 252 | 'Level up! Choose a stat to raise:\n', 253 | ['Constitution (+20 HP, from ' + str(player.fighter.max_hp) + ')', 254 | 'Strength (+1 attack, from ' + str(player.fighter.power) + ')', 255 | 'Agility (+1 defense, from ' + str(player.fighter.defense) + ')'], 256 | LEVEL_SCREEN_WIDTH) 257 | 258 | if choice == 0: 259 | player.fighter.base_max_hp += 20 260 | player.fighter.hp += 20 261 | elif choice == 1: 262 | player.fighter.base_power += 1 263 | elif choice == 2: 264 | player.fighter.base_defense += 1 265 | 266 | 267 | def player_death(player): 268 | """ 269 | End the game! 270 | """ 271 | log.message('You died!', libtcod.red) 272 | player.game_state = 'dead' 273 | 274 | # For added effect, transform the player into a corpse! 275 | player.char = '%' 276 | player.color = libtcod.dark_red 277 | 278 | 279 | def save_game(player): 280 | """ 281 | Save the game to file "savegame"; 282 | overwrites any existing data. 283 | """ 284 | file = shelve.open('savegame', 'n') 285 | file['current_map'] = player.current_map 286 | file['player_index'] = player.current_map.objects.index(player) 287 | file['game_msgs'] = log.game_msgs 288 | file.close() 289 | 290 | 291 | def load_game(): 292 | """ 293 | Loads from "savegame". 294 | Returns the player object. 295 | """ 296 | file = shelve.open('savegame', 'r') 297 | current_map = file['current_map'] 298 | player = current_map.objects[file['player_index']] 299 | log.game_msgs = file['game_msgs'] 300 | file.close() 301 | 302 | current_map.initialize_fov() 303 | 304 | return player 305 | 306 | 307 | def new_game(): 308 | """ 309 | Starts a new game, with a default player on level 1 of the dungeon. 310 | Returns the player object. 311 | """ 312 | # Must initialize the log before we do anything that might emit a message. 313 | log.init() 314 | 315 | fighter_component = Fighter(hp=100, defense=1, power=2, xp=0, death_function=player_death) 316 | player = Object(algebra.Location(0, 0), '@', 'player', libtcod.white, blocks=True, fighter=fighter_component) 317 | player.inventory = [] 318 | player.level = 1 319 | player.game_state = 'playing' 320 | # True if there's a (hostile) fighter in FOV 321 | player.endangered = False 322 | 323 | obj = miscellany.dagger() 324 | player.inventory.append(obj) 325 | actions.equip(player, obj.equipment, False) 326 | obj.always_visible = True 327 | 328 | cartographer.make_map(player, 1) 329 | renderer.clear_console() 330 | renderer.update_camera(player) 331 | 332 | log.message('Welcome stranger! Prepare to perish in the Tombs of the Ancient Kings.', libtcod.red) 333 | log.message('Press ? or F1 for help.') 334 | 335 | return player 336 | 337 | 338 | def next_level(player, portal): 339 | """ 340 | Advance to the next level (changing player.current_map). 341 | Heals the player 50%. 342 | Returns the Map of the new level. 343 | """ 344 | log.message('You take a moment to rest, and recover your strength.', libtcod.light_violet) 345 | actions.heal(player.fighter, player.fighter.max_hp / 2) 346 | 347 | log.message('After a rare moment of peace, you descend deeper into the heart of the dungeon...', libtcod.red) 348 | old_map = player.current_map 349 | cartographer.make_map(player, player.current_map.dungeon_level + 1) 350 | renderer.clear_console() 351 | renderer.update_camera(player) 352 | 353 | # Create the up stairs at the current position. 354 | stairs = Object(player.pos, '>', 'stairs up', libtcod.white, always_visible=True) 355 | stairs.destination = old_map 356 | stairs.dest_position = portal.pos 357 | player.current_map.objects.insert(0, stairs) 358 | player.current_map.portals.insert(0, stairs) 359 | 360 | return player.current_map 361 | 362 | 363 | def revisit_level(player, portal): 364 | """ 365 | Return to a level the player has previously visited (changing player.current_map). 366 | Does *not* heal the player. 367 | """ 368 | player.current_map = portal.destination 369 | player.pos = portal.dest_position 370 | # Call to initialize_fov() should be redundant but in practice seems to have 371 | # worked around an intermittent bug. 372 | player.current_map.initialize_fov() 373 | player.current_map.fov_needs_recompute = True 374 | renderer.update_camera(player) 375 | renderer.clear_console() 376 | 377 | 378 | def process_visible_objects(player): 379 | """ 380 | We will show the object if it's visible to the player 381 | or it's set to "always visible" and on an explored tile. 382 | If we're showing a hostile monster, set player.endangered. 383 | """ 384 | player.endangered = False 385 | visible_objects = [] 386 | for o in player.current_map.objects: 387 | if o == player: 388 | continue 389 | if (libtcod.map_is_in_fov(player.current_map.fov_map, o.x, o.y) or 390 | (o.always_visible and 391 | player.current_map.is_explored(o.pos))): 392 | visible_objects.append(o) 393 | if o.fighter: 394 | player.endangered = True 395 | return visible_objects 396 | 397 | 398 | def play_game(player): 399 | """ 400 | Main loop. 401 | """ 402 | player_action = None 403 | 404 | while not libtcod.console_is_window_closed(): 405 | (key, mouse) = interface.poll() 406 | player.visible_objects = process_visible_objects(player) 407 | renderer.render_all(player, (mouse.cx, mouse.cy)) 408 | player.current_map.fov_needs_recompute = False 409 | 410 | libtcod.console_flush() 411 | 412 | check_level_up(player) 413 | 414 | # Erase all objects at their old locations, before they move. 415 | for object in player.current_map.objects: 416 | renderer.clear_object(player, object) 417 | 418 | player_action = handle_keys(player, key) 419 | if player_action == 'exit': 420 | save_game(player) 421 | break 422 | 423 | if (player_action != 'didnt-take-turn' and 424 | (player.game_state == 'playing' or 425 | player.game_state == 'running')): 426 | for object in player.current_map.objects: 427 | if object.ai: 428 | object.ai.take_turn(player) 429 | 430 | if __name__ == '__main__': 431 | renderer.renderer_init() 432 | # cProfile.run('renderer.main_menu(new_game, play_game, load_game)') 433 | renderer.main_menu(new_game, play_game, load_game) 434 | -------------------------------------------------------------------------------- /renderer.py: -------------------------------------------------------------------------------- 1 | import libtcodpy as libtcod 2 | import time 3 | 4 | import config 5 | import log 6 | import algebra 7 | import map 8 | 9 | 10 | FOV_ALGO = 0 11 | FOV_LIGHT_WALLS = True 12 | TORCH_RADIUS = 10 13 | 14 | PANEL_Y = config.SCREEN_HEIGHT - config.PANEL_HEIGHT 15 | MSG_X = config.BAR_WIDTH + 2 16 | 17 | LIMIT_FPS = 20 18 | 19 | _frame_index = 0 20 | _twenty_frame_estimate = 1000 21 | _last_frame_time = None 22 | 23 | 24 | _con = None 25 | """ main console window for drawing the map and objects """ 26 | _overlay = None 27 | """ buffer overlaid over the main console window for effects, 28 | labels, and other metadata. 29 | """ 30 | _panel = None 31 | """ UI text data """ 32 | 33 | 34 | 35 | 36 | _console_center = algebra.Location(config.MAP_PANEL_WIDTH / 2, 37 | config.MAP_PANEL_HEIGHT / 2) 38 | 39 | 40 | def block_for_key(): 41 | """ 42 | Approximately replacing libtcod.console_wait_for_keypress(), 43 | returns a libtcod.Key object. 44 | """ 45 | key = libtcod.Key() 46 | mouse = libtcod.Mouse() 47 | while True: 48 | libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) 49 | if (key.vk == libtcod.KEY_NONE): 50 | continue 51 | 52 | if (key.vk == libtcod.KEY_ALT or 53 | key.vk == libtcod.KEY_SHIFT or 54 | key.vk == libtcod.KEY_CONTROL): 55 | continue 56 | 57 | break 58 | return key 59 | 60 | 61 | class ScreenCoords(tuple): 62 | @staticmethod 63 | def fromWorldCoords(camera_coords, world_coords): 64 | """ 65 | Returns (None, None) if the specified world coordinates would be off-screen. 66 | """ 67 | x = world_coords.x - camera_coords.x 68 | y = world_coords.y - camera_coords.y 69 | if (x < 0 or y < 0 or x >= config.MAP_PANEL_WIDTH or y >= config.MAP_PANEL_HEIGHT): 70 | return ScreenCoords((None, None)) 71 | return ScreenCoords((x, y)) 72 | 73 | @staticmethod 74 | def toWorldCoords(camera_coords, screen_coords): 75 | x = screen_coords[0] + camera_coords.x 76 | y = screen_coords[1] + camera_coords.y 77 | return algebra.Location(x, y) 78 | 79 | 80 | def renderer_init(): 81 | """ 82 | Initialize libtcod and set up our basic consoles to draw into. 83 | """ 84 | global _con, _panel, _overlay, _last_frame_time 85 | libtcod.console_set_custom_font('arial12x12.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) 86 | libtcod.console_init_root(config.SCREEN_WIDTH, config.SCREEN_HEIGHT, 'python/libtcod tutorial', False) 87 | libtcod.sys_set_fps(LIMIT_FPS) 88 | _con = libtcod.console_new(config.MAP_PANEL_WIDTH, config.MAP_PANEL_HEIGHT) 89 | _overlay = libtcod.console_new(config.MAP_PANEL_WIDTH, config.MAP_PANEL_HEIGHT) 90 | _panel = libtcod.console_new(config.SCREEN_WIDTH, config.PANEL_HEIGHT) 91 | _last_frame_time = time.time() * 1000 92 | 93 | 94 | def parse_move(key): 95 | """ 96 | Returns (bool, direction, bool). 97 | First value is True if a direction key was pressed, False otherwise. 98 | Direction will be None if first value is False or the '.' or numpad 5 were pressed. 99 | Last value is True if shift was held (run / page-scroll), False otherwise. 100 | """ 101 | key_char = chr(key.c) 102 | if (key.vk == libtcod.KEY_UP or key.vk == libtcod.KEY_KP8 or 103 | key_char == 'k' or key_char == 'K'): 104 | return (True, algebra.north, key.shift) 105 | elif (key.vk == libtcod.KEY_DOWN or key.vk == libtcod.KEY_KP2 or 106 | key_char == 'j' or key_char == 'J'): 107 | return (True, algebra.south, key.shift) 108 | elif (key.vk == libtcod.KEY_LEFT or key.vk == libtcod.KEY_KP4 or 109 | key_char == 'h' or key_char == 'H'): 110 | return (True, algebra.west, key.shift) 111 | elif (key.vk == libtcod.KEY_RIGHT or key.vk == libtcod.KEY_KP6 or 112 | key_char == 'l' or key_char == 'L'): 113 | return (True, algebra.east, key.shift) 114 | elif (key.vk == libtcod.KEY_HOME or key.vk == libtcod.KEY_KP7 or 115 | key_char == 'y' or key_char == 'Y'): 116 | return (True, algebra.northwest, key.shift) 117 | elif (key.vk == libtcod.KEY_PAGEUP or key.vk == libtcod.KEY_KP9 or 118 | key_char == 'u' or key_char == 'U'): 119 | return (True, algebra.northeast, key.shift) 120 | elif (key.vk == libtcod.KEY_END or key.vk == libtcod.KEY_KP1 or 121 | key_char == 'b' or key_char == 'B'): 122 | return (True, algebra.southwest, key.shift) 123 | elif (key.vk == libtcod.KEY_PAGEDOWN or key.vk == libtcod.KEY_KP3 or 124 | key_char == 'n' or key_char == 'N'): 125 | return (True, algebra.southeast, key.shift) 126 | elif (key.vk == libtcod.KEY_KP5 or key_char == '.'): 127 | # do nothing but note that a relevant key was pressed 128 | return (True, None, False) 129 | return (False, None, False) 130 | 131 | 132 | def msgbox(text, width=50): 133 | """ 134 | Display a message, wait for any keypress. 135 | """ 136 | menu(text, [], width) 137 | 138 | 139 | def write_log(messages, window, x, initial_y): 140 | y = initial_y 141 | for m in messages: 142 | libtcod.console_set_default_foreground(window, m.color) 143 | line = m.message 144 | if m.count > 1: 145 | line += ' (x' + str(m.count) + ')' 146 | libtcod.console_print_ex(window, x, y, libtcod.BKGND_NONE, 147 | libtcod.LEFT, line) 148 | y += 1 149 | 150 | 151 | def main_menu(new_game, play_game, load_game): 152 | """ 153 | Prompt the player to start a new game, continue playing the last game, 154 | or exit. 155 | """ 156 | img = libtcod.image_load('menu_background.png') 157 | 158 | while not libtcod.console_is_window_closed(): 159 | # Show the background image, at twice the regular console resolution. 160 | libtcod.image_blit_2x(img, 0, 0, 0) 161 | 162 | libtcod.console_set_default_foreground(0, libtcod.light_yellow) 163 | libtcod.console_print_ex( 164 | 0, config.SCREEN_WIDTH/2, config.SCREEN_HEIGHT/2-4, libtcod.BKGND_NONE, 165 | libtcod.CENTER, 'TOMBS OF THE ANCIENT KINGS') 166 | libtcod.console_print_ex( 167 | 0, config.SCREEN_WIDTH/2, config.SCREEN_HEIGHT-2, libtcod.BKGND_NONE, 168 | libtcod.CENTER, 'By Jotaf') 169 | 170 | (char, choice) = menu('', ['Play a new game', 'Continue last game', 'Quit'], 24) 171 | 172 | if choice == 0: 173 | play_game(new_game()) 174 | if choice == 1: 175 | try: 176 | player = load_game() 177 | except: 178 | msgbox('\n No saved game to load.\n', 24) 179 | continue 180 | play_game(player) 181 | elif choice == 2: 182 | break 183 | 184 | 185 | def clear_console(): 186 | global _con 187 | libtcod.console_clear(_con) 188 | 189 | 190 | def _render_bar(x, y, total_width, name, value, maximum, bar_color, back_color): 191 | bar_width = int(float(value) / maximum * total_width) 192 | 193 | libtcod.console_set_default_background(_panel, back_color) 194 | libtcod.console_rect(_panel, x, y, total_width, 1, False, libtcod.BKGND_SCREEN) 195 | 196 | libtcod.console_set_default_background(_panel, bar_color) 197 | if bar_width > 0: 198 | libtcod.console_rect(_panel, x, y, bar_width, 1, False, libtcod.BKGND_SCREEN) 199 | 200 | libtcod.console_set_default_foreground(_panel, libtcod.white) 201 | libtcod.console_print_ex( 202 | _panel, x + total_width / 2, y, libtcod.BKGND_NONE, libtcod.CENTER, 203 | name + ': ' + str(value) + '/' + str(maximum)) 204 | 205 | 206 | def _describe_obj(obj): 207 | """ 208 | Prints the name of the object, and any qualifiers. 209 | - if an item with count > 1, appends the count 210 | """ 211 | if obj.item and obj.item.count > 1: 212 | return obj.name + ' (x' + str(obj.item.count) + ')' 213 | else: 214 | return obj.name 215 | 216 | 217 | def _get_names_under_mouse(player, (sx, sy)): 218 | if (sx < 0 or sy < 0 or 219 | sx >= config.MAP_PANEL_WIDTH or 220 | sy >= config.MAP_PANEL_HEIGHT): 221 | return '' 222 | 223 | objects = player.current_map.objects 224 | fov_map = player.current_map.fov_map 225 | pos = ScreenCoords.toWorldCoords(player.camera_position, 226 | (sx, sy)) 227 | if (pos.x >= player.current_map.width or 228 | pos.y >= player.current_map.height): 229 | return '' 230 | 231 | names = [_describe_obj(obj) for obj in objects 232 | if obj.pos == pos and 233 | libtcod.map_is_in_fov(fov_map, obj.x, obj.y)] 234 | if player.current_map.terrain_at(pos).display_name: 235 | names.append(player.current_map.terrain_at(pos).display_name) 236 | 237 | names = ', '.join(names) 238 | return names.capitalize() 239 | 240 | 241 | def _draw_object(player, o): 242 | global _con 243 | libtcod.console_set_default_foreground(_con, o.color) 244 | (x, y) = ScreenCoords.fromWorldCoords(player.camera_position, o.pos) 245 | libtcod.console_put_char(_con, x, y, o.char, libtcod.BKGND_NONE) 246 | 247 | 248 | def clear_object(player, o): 249 | """ 250 | Erase the character that represents this object. 251 | """ 252 | global _con 253 | (x, y) = ScreenCoords.fromWorldCoords(player.camera_position, o.pos) 254 | libtcod.console_put_char(_con, x, y, ' ', libtcod.BKGND_NONE) 255 | 256 | 257 | def menu(header, options, width): 258 | """ 259 | Display a menu of options headed by letters; return (the key pressed, the index [0, 25] of the selection, or None). 260 | """ 261 | global _con 262 | if len(options) > 26: 263 | raise ValueError('Cannot have a menu with more than 26 options.') 264 | 265 | # Calculate total height for the header (after auto-wrap) and one line per option. 266 | header_height = libtcod.console_get_height_rect(_con, 0, 0, width, config.SCREEN_HEIGHT, header) 267 | if header == '': 268 | header_height = 0 269 | height = len(options) + header_height 270 | 271 | # Create an off-screen console that represents the menu's window. 272 | window = libtcod.console_new(width, height) 273 | 274 | libtcod.console_set_default_foreground(window, libtcod.white) 275 | libtcod.console_print_rect_ex(window, 0, 0, width, height, libtcod.BKGND_NONE, libtcod.LEFT, header) 276 | 277 | y = header_height 278 | letter_index = ord('a') 279 | for option_text in options: 280 | text = '(' + chr(letter_index) + ') ' + option_text 281 | libtcod.console_print_ex(window, 0, y, libtcod.BKGND_NONE, libtcod.LEFT, text) 282 | y += 1 283 | letter_index += 1 284 | 285 | x = config.SCREEN_WIDTH/2 - width/2 286 | y = config.SCREEN_HEIGHT/2 - height/2 287 | libtcod.console_blit(window, 0, 0, width, height, 0, x, y, 1.0, 0.7) 288 | 289 | libtcod.console_flush() 290 | while True: 291 | key = block_for_key() 292 | if not (key.vk == libtcod.KEY_ALT or key.vk == libtcod.KEY_CONTROL or 293 | key.vk == libtcod.KEY_SHIFT): 294 | break 295 | 296 | index = key.c - ord('a') 297 | if index >= 0 and index < len(options): 298 | return (key.c, index) 299 | return (key.c, None) 300 | 301 | 302 | def _draw_fov_using_terrain(player): 303 | """ 304 | Overly optimized: this code inlines Map.terrain_at(), Map.is_explored(), 305 | and ScreenCoords.toWorldCoords() in order to get a 2.5x speedup on 306 | large maps. 307 | """ 308 | libtcod.console_clear(_con) 309 | current_map = player.current_map 310 | pos = algebra.Location(0, 0) 311 | for screen_y in range(min(current_map.height, config.MAP_PANEL_HEIGHT)): 312 | pos.set(player.camera_position.x, player.camera_position.y + screen_y) 313 | for screen_x in range(min(current_map.width, config.MAP_PANEL_WIDTH)): 314 | # pos = ScreenCoords.toWorldCoords(player.camera_position, (screen_x, screen_y)) 315 | visible = libtcod.map_is_in_fov(current_map.fov_map, pos.x, pos.y) 316 | # terrain = current_map.terrain_at(pos) 317 | terrain = map.terrain_types[current_map.terrain[pos.x][pos.y]] 318 | if not visible: 319 | # if current_map.is_explored(pos): 320 | if current_map._explored[pos.x][pos.y]: 321 | libtcod.console_set_char_background(_con, screen_x, screen_y, 322 | terrain.unseen_color, libtcod.BKGND_SET) 323 | else: 324 | libtcod.console_set_char_background(_con, screen_x, screen_y, 325 | terrain.seen_color, libtcod.BKGND_SET) 326 | current_map.explore(pos) 327 | pos.x += 1 328 | 329 | 330 | def update_camera(player): 331 | """ 332 | Makes sure the player is roughly centered and that we're not trying to draw off screen. 333 | Basic implementation is stateless. 334 | """ 335 | newPos = player.pos - _console_center 336 | 337 | # Make sure the camera doesn't see outside the map. 338 | newPos.bound(algebra.Rect(0, 0, 339 | player.current_map.width - config.MAP_PANEL_WIDTH, 340 | player.current_map.height - config.MAP_PANEL_HEIGHT)) 341 | 342 | if newPos != player.camera_position: 343 | player.current_map.fov_needs_recompute = True 344 | player.camera_position = newPos 345 | 346 | 347 | def _debug_positions(player, mouse): 348 | global _panel 349 | libtcod.console_print_ex( 350 | _panel, 15, 4, libtcod.BKGND_NONE, 351 | libtcod.RIGHT, ' @ ' + player.pos.to_string()) 352 | libtcod.console_print_ex( 353 | _panel, 15, 5, libtcod.BKGND_NONE, 354 | libtcod.RIGHT, ' m ' + str(mouse.cx) + ', ' + str(mouse.cy)) 355 | libtcod.console_print_ex( 356 | _panel, 15, 6, libtcod.BKGND_NONE, 357 | libtcod.RIGHT, 'cam ' + player.camera_position.to_string()) 358 | 359 | 360 | def _debug_room(player): 361 | global _panel 362 | room_index = -1 363 | for r in player.current_map.rooms: 364 | if r.contains(player.pos): 365 | room_index = player.current_map.rooms.index(r) 366 | break 367 | if (room_index != -1): 368 | libtcod.console_print_ex( 369 | _panel, 1, 4, libtcod.BKGND_NONE, 370 | libtcod.LEFT, 'Room ' + str(room_index + 1)) 371 | 372 | 373 | def _debug_danger(player): 374 | global _panel 375 | if player.endangered: 376 | libtcod.console_print_ex( 377 | _panel, 1, 2, libtcod.BKGND_NONE, 378 | libtcod.LEFT, 'DANGER') 379 | 380 | 381 | def _debug_fps(): 382 | global _panel, _twenty_frame_estimate 383 | libtcod.console_print_ex(_panel, 1, 2, libtcod.BKGND_NONE, libtcod.LEFT, 384 | 'FPS ' + str(20000. / _twenty_frame_estimate)) 385 | 386 | 387 | def draw_console(player): 388 | """ 389 | Refreshes the map display and blits to the window. 390 | Sets or clears player.endangered. 391 | """ 392 | global _con 393 | 394 | current_map = player.current_map 395 | 396 | if current_map.fov_needs_recompute: 397 | # Recompute FOV if needed (the player moved or something in 398 | # the dungeon changed). 399 | libtcod.map_compute_fov( 400 | current_map.fov_map, player.x, 401 | player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO) 402 | _draw_fov_using_terrain(player) 403 | 404 | # Draw all objects in the list, except the player. We want it to 405 | # always appear over all other objects, so it's drawn later. 406 | # (Could also achieve this by guaranteeing the player is always 407 | # the last object in current_map.objects.) 408 | for object in player.visible_objects: 409 | _draw_object(player, object) 410 | _draw_object(player, player) 411 | 412 | libtcod.console_blit(_con, 0, 0, config.MAP_PANEL_WIDTH, 413 | config.MAP_PANEL_HEIGHT, 0, 0, 0) 414 | 415 | 416 | def draw_panel(player, pointer_location): 417 | """ 418 | Refreshes the UI display and blits it to the window. 419 | """ 420 | libtcod.console_set_default_background(_panel, libtcod.black) 421 | libtcod.console_clear(_panel) 422 | 423 | # Only display the (log.MSG_HEIGHT) most recent 424 | write_log(log.game_msgs[-log.MSG_HEIGHT:], _panel, MSG_X, 1) 425 | 426 | _render_bar(1, 1, config.BAR_WIDTH, 'HP', player.fighter.hp, 427 | player.fighter.max_hp, 428 | libtcod.light_red, libtcod.darker_red) 429 | libtcod.console_print_ex( 430 | _panel, 1, 3, libtcod.BKGND_NONE, 431 | libtcod.LEFT, 'Dungeon level ' + str(player.current_map.dungeon_level)) 432 | # _debug_positions(player, mouse) 433 | # _debug_room(player) 434 | # _debug_danger(player) 435 | _debug_fps() 436 | 437 | libtcod.console_set_default_foreground(_panel, libtcod.light_gray) 438 | libtcod.console_print_ex( 439 | _panel, 1, 0, libtcod.BKGND_NONE, libtcod.LEFT, 440 | _get_names_under_mouse(player, pointer_location)) 441 | 442 | # Done with "_panel", blit it to the root console. 443 | libtcod.console_blit(_panel, 0, 0, config.SCREEN_WIDTH, config.PANEL_HEIGHT, 444 | 0, 0, PANEL_Y) 445 | 446 | 447 | def blit_overlay(): 448 | global _overlay 449 | libtcod.console_set_key_color(_overlay, libtcod.black) 450 | libtcod.console_blit(_overlay, 0, 0, config.MAP_PANEL_WIDTH, 451 | config.MAP_PANEL_HEIGHT, 0, 0, 0, 0.4, 1.0) 452 | 453 | 454 | def render_all(player, pointer_location): 455 | global _frame_index, _twenty_frame_estimate, _last_frame_time 456 | update_camera(player) 457 | _frame_index = (_frame_index + 1) % 20 458 | if _frame_index == 0: 459 | now = time.time() * 1000 460 | _twenty_frame_estimate = (now - _last_frame_time) / 2 + (_twenty_frame_estimate / 2) 461 | _last_frame_time = now 462 | 463 | draw_console(player) 464 | draw_panel(player, pointer_location) 465 | blit_overlay() 466 | -------------------------------------------------------------------------------- /libtcodpy.py: -------------------------------------------------------------------------------- 1 | # 2 | # libtcod 1.5.1 python wrapper 3 | # Copyright (c) 2008,2009,2010 Jice & Mingos 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # * Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # * Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # * The name of Jice or Mingos may not be used to endorse or promote products 14 | # derived from this software without specific prior written permission. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY JICE AND MINGOS ``AS IS'' AND ANY 17 | # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | # DISCLAIMED. IN NO EVENT SHALL JICE OR MINGOS BE LIABLE FOR ANY 20 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | # 27 | 28 | import sys 29 | import ctypes 30 | import struct 31 | from ctypes import * 32 | 33 | if not hasattr(ctypes, "c_bool"): # for Python < 2.6 34 | c_bool = c_uint8 35 | 36 | try: #import NumPy if available 37 | import numpy 38 | numpy_available = True 39 | except ImportError: 40 | numpy_available = False 41 | 42 | LINUX=False 43 | MAC=False 44 | MINGW=False 45 | MSVC=False 46 | if sys.platform.find('linux') != -1: 47 | _lib = ctypes.cdll['./libtcod.so'] 48 | LINUX=True 49 | elif sys.platform.find('darwin') != -1: 50 | _lib = ctypes.cdll['./libtcod.dylib'] 51 | MAC = True 52 | elif sys.platform.find('haiku') != -1: 53 | _lib = ctypes.cdll['./libtcod.so'] 54 | HAIKU = True 55 | else: 56 | try: 57 | _lib = ctypes.cdll['./libtcod-mingw.dll'] 58 | MINGW=True 59 | except WindowsError: 60 | _lib = ctypes.cdll['./libtcod-VS.dll'] 61 | MSVC=True 62 | # On Windows, ctypes doesn't work well with function returning structs, 63 | # so we have to user the _wrapper functions instead 64 | _lib.TCOD_color_multiply = _lib.TCOD_color_multiply_wrapper 65 | _lib.TCOD_color_add = _lib.TCOD_color_add_wrapper 66 | _lib.TCOD_color_multiply_scalar = _lib.TCOD_color_multiply_scalar_wrapper 67 | _lib.TCOD_color_subtract = _lib.TCOD_color_subtract_wrapper 68 | _lib.TCOD_color_lerp = _lib.TCOD_color_lerp_wrapper 69 | _lib.TCOD_console_get_default_background = _lib.TCOD_console_get_default_background_wrapper 70 | _lib.TCOD_console_get_default_foreground = _lib.TCOD_console_get_default_foreground_wrapper 71 | _lib.TCOD_console_get_char_background = _lib.TCOD_console_get_char_background_wrapper 72 | _lib.TCOD_console_get_char_foreground = _lib.TCOD_console_get_char_foreground_wrapper 73 | _lib.TCOD_console_get_fading_color = _lib.TCOD_console_get_fading_color_wrapper 74 | _lib.TCOD_image_get_pixel = _lib.TCOD_image_get_pixel_wrapper 75 | _lib.TCOD_image_get_mipmap_pixel = _lib.TCOD_image_get_mipmap_pixel_wrapper 76 | _lib.TCOD_parser_get_color_property = _lib.TCOD_parser_get_color_property_wrapper 77 | 78 | HEXVERSION = 0x010501 79 | STRVERSION = "1.5.1" 80 | TECHVERSION = 0x01050103 81 | 82 | ############################ 83 | # color module 84 | ############################ 85 | class Color(Structure): 86 | _fields_ = [('r', c_uint8), 87 | ('g', c_uint8), 88 | ('b', c_uint8), 89 | ] 90 | 91 | def __eq__(self, c): 92 | return _lib.TCOD_color_equals(self, c) 93 | 94 | def __mul__(self, c): 95 | if isinstance(c,Color): 96 | return _lib.TCOD_color_multiply(self, c) 97 | else: 98 | return _lib.TCOD_color_multiply_scalar(self, c_float(c)) 99 | 100 | def __add__(self, c): 101 | return _lib.TCOD_color_add(self, c) 102 | 103 | def __sub__(self, c): 104 | return _lib.TCOD_color_subtract(self, c) 105 | 106 | def __repr__(self): 107 | return "Color(%d,%d,%d)" % (self.r, self.g, self.b) 108 | 109 | def __getitem__(self, i): 110 | if type(i) == str: 111 | return getattr(self, i) 112 | else: 113 | return getattr(self, "rgb"[i]) 114 | 115 | def __setitem__(self, i, c): 116 | if type(i) == str: 117 | setattr(self, i, c) 118 | else: 119 | setattr(self, "rgb"[i], c) 120 | 121 | def __iter__(self): 122 | yield self.r 123 | yield self.g 124 | yield self.b 125 | 126 | # Should be valid on any platform, check it! Has to be done after Color is defined. 127 | if MAC: 128 | from cprotos import setup_protos 129 | setup_protos(_lib) 130 | 131 | _lib.TCOD_color_equals.restype = c_bool 132 | _lib.TCOD_color_multiply.restype = Color 133 | _lib.TCOD_color_multiply_scalar.restype = Color 134 | _lib.TCOD_color_add.restype = Color 135 | _lib.TCOD_color_subtract.restype = Color 136 | 137 | # default colors 138 | # grey levels 139 | black=Color(0,0,0) 140 | darkest_grey=Color(31,31,31) 141 | darker_grey=Color(63,63,63) 142 | dark_grey=Color(95,95,95) 143 | grey=Color(127,127,127) 144 | light_grey=Color(159,159,159) 145 | lighter_grey=Color(191,191,191) 146 | lightest_grey=Color(223,223,223) 147 | darkest_gray=Color(31,31,31) 148 | darker_gray=Color(63,63,63) 149 | dark_gray=Color(95,95,95) 150 | gray=Color(127,127,127) 151 | light_gray=Color(159,159,159) 152 | lighter_gray=Color(191,191,191) 153 | lightest_gray=Color(223,223,223) 154 | white=Color(255,255,255) 155 | 156 | # sepia 157 | darkest_sepia=Color(31,24,15) 158 | darker_sepia=Color(63,50,31) 159 | dark_sepia=Color(94,75,47) 160 | sepia=Color(127,101,63) 161 | light_sepia=Color(158,134,100) 162 | lighter_sepia=Color(191,171,143) 163 | lightest_sepia=Color(222,211,195) 164 | 165 | #standard colors 166 | red=Color(255,0,0) 167 | flame=Color(255,63,0) 168 | orange=Color(255,127,0) 169 | amber=Color(255,191,0) 170 | yellow=Color(255,255,0) 171 | lime=Color(191,255,0) 172 | chartreuse=Color(127,255,0) 173 | green=Color(0,255,0) 174 | sea=Color(0,255,127) 175 | turquoise=Color(0,255,191) 176 | cyan=Color(0,255,255) 177 | sky=Color(0,191,255) 178 | azure=Color(0,127,255) 179 | blue=Color(0,0,255) 180 | han=Color(63,0,255) 181 | violet=Color(127,0,255) 182 | purple=Color(191,0,255) 183 | fuchsia=Color(255,0,255) 184 | magenta=Color(255,0,191) 185 | pink=Color(255,0,127) 186 | crimson=Color(255,0,63) 187 | 188 | # dark colors 189 | dark_red=Color(191,0,0) 190 | dark_flame=Color(191,47,0) 191 | dark_orange=Color(191,95,0) 192 | dark_amber=Color(191,143,0) 193 | dark_yellow=Color(191,191,0) 194 | dark_lime=Color(143,191,0) 195 | dark_chartreuse=Color(95,191,0) 196 | dark_green=Color(0,191,0) 197 | dark_sea=Color(0,191,95) 198 | dark_turquoise=Color(0,191,143) 199 | dark_cyan=Color(0,191,191) 200 | dark_sky=Color(0,143,191) 201 | dark_azure=Color(0,95,191) 202 | dark_blue=Color(0,0,191) 203 | dark_han=Color(47,0,191) 204 | dark_violet=Color(95,0,191) 205 | dark_purple=Color(143,0,191) 206 | dark_fuchsia=Color(191,0,191) 207 | dark_magenta=Color(191,0,143) 208 | dark_pink=Color(191,0,95) 209 | dark_crimson=Color(191,0,47) 210 | 211 | # darker colors 212 | darker_red=Color(127,0,0) 213 | darker_flame=Color(127,31,0) 214 | darker_orange=Color(127,63,0) 215 | darker_amber=Color(127,95,0) 216 | darker_yellow=Color(127,127,0) 217 | darker_lime=Color(95,127,0) 218 | darker_chartreuse=Color(63,127,0) 219 | darker_green=Color(0,127,0) 220 | darker_sea=Color(0,127,63) 221 | darker_turquoise=Color(0,127,95) 222 | darker_cyan=Color(0,127,127) 223 | darker_sky=Color(0,95,127) 224 | darker_azure=Color(0,63,127) 225 | darker_blue=Color(0,0,127) 226 | darker_han=Color(31,0,127) 227 | darker_violet=Color(63,0,127) 228 | darker_purple=Color(95,0,127) 229 | darker_fuchsia=Color(127,0,127) 230 | darker_magenta=Color(127,0,95) 231 | darker_pink=Color(127,0,63) 232 | darker_crimson=Color(127,0,31) 233 | 234 | # darkest colors 235 | darkest_red=Color(63,0,0) 236 | darkest_flame=Color(63,15,0) 237 | darkest_orange=Color(63,31,0) 238 | darkest_amber=Color(63,47,0) 239 | darkest_yellow=Color(63,63,0) 240 | darkest_lime=Color(47,63,0) 241 | darkest_chartreuse=Color(31,63,0) 242 | darkest_green=Color(0,63,0) 243 | darkest_sea=Color(0,63,31) 244 | darkest_turquoise=Color(0,63,47) 245 | darkest_cyan=Color(0,63,63) 246 | darkest_sky=Color(0,47,63) 247 | darkest_azure=Color(0,31,63) 248 | darkest_blue=Color(0,0,63) 249 | darkest_han=Color(15,0,63) 250 | darkest_violet=Color(31,0,63) 251 | darkest_purple=Color(47,0,63) 252 | darkest_fuchsia=Color(63,0,63) 253 | darkest_magenta=Color(63,0,47) 254 | darkest_pink=Color(63,0,31) 255 | darkest_crimson=Color(63,0,15) 256 | 257 | # light colors 258 | light_red=Color(255,114,114) 259 | light_flame=Color(255,149,114) 260 | light_orange=Color(255,184,114) 261 | light_amber=Color(255,219,114) 262 | light_yellow=Color(255,255,114) 263 | light_lime=Color(219,255,114) 264 | light_chartreuse=Color(184,255,114) 265 | light_green=Color(114,255,114) 266 | light_sea=Color(114,255,184) 267 | light_turquoise=Color(114,255,219) 268 | light_cyan=Color(114,255,255) 269 | light_sky=Color(114,219,255) 270 | light_azure=Color(114,184,255) 271 | light_blue=Color(114,114,255) 272 | light_han=Color(149,114,255) 273 | light_violet=Color(184,114,255) 274 | light_purple=Color(219,114,255) 275 | light_fuchsia=Color(255,114,255) 276 | light_magenta=Color(255,114,219) 277 | light_pink=Color(255,114,184) 278 | light_crimson=Color(255,114,149) 279 | 280 | #lighter colors 281 | lighter_red=Color(255,165,165) 282 | lighter_flame=Color(255,188,165) 283 | lighter_orange=Color(255,210,165) 284 | lighter_amber=Color(255,232,165) 285 | lighter_yellow=Color(255,255,165) 286 | lighter_lime=Color(232,255,165) 287 | lighter_chartreuse=Color(210,255,165) 288 | lighter_green=Color(165,255,165) 289 | lighter_sea=Color(165,255,210) 290 | lighter_turquoise=Color(165,255,232) 291 | lighter_cyan=Color(165,255,255) 292 | lighter_sky=Color(165,232,255) 293 | lighter_azure=Color(165,210,255) 294 | lighter_blue=Color(165,165,255) 295 | lighter_han=Color(188,165,255) 296 | lighter_violet=Color(210,165,255) 297 | lighter_purple=Color(232,165,255) 298 | lighter_fuchsia=Color(255,165,255) 299 | lighter_magenta=Color(255,165,232) 300 | lighter_pink=Color(255,165,210) 301 | lighter_crimson=Color(255,165,188) 302 | 303 | # lightest colors 304 | lightest_red=Color(255,191,191) 305 | lightest_flame=Color(255,207,191) 306 | lightest_orange=Color(255,223,191) 307 | lightest_amber=Color(255,239,191) 308 | lightest_yellow=Color(255,255,191) 309 | lightest_lime=Color(239,255,191) 310 | lightest_chartreuse=Color(223,255,191) 311 | lightest_green=Color(191,255,191) 312 | lightest_sea=Color(191,255,223) 313 | lightest_turquoise=Color(191,255,239) 314 | lightest_cyan=Color(191,255,255) 315 | lightest_sky=Color(191,239,255) 316 | lightest_azure=Color(191,223,255) 317 | lightest_blue=Color(191,191,255) 318 | lightest_han=Color(207,191,255) 319 | lightest_violet=Color(223,191,255) 320 | lightest_purple=Color(239,191,255) 321 | lightest_fuchsia=Color(255,191,255) 322 | lightest_magenta=Color(255,191,239) 323 | lightest_pink=Color(255,191,223) 324 | lightest_crimson=Color(255,191,207) 325 | 326 | # desaturated colors 327 | desaturated_red=Color(127,63,63) 328 | desaturated_flame=Color(127,79,63) 329 | desaturated_orange=Color(127,95,63) 330 | desaturated_amber=Color(127,111,63) 331 | desaturated_yellow=Color(127,127,63) 332 | desaturated_lime=Color(111,127,63) 333 | desaturated_chartreuse=Color(95,127,63) 334 | desaturated_green=Color(63,127,63) 335 | desaturated_sea=Color(63,127,95) 336 | desaturated_turquoise=Color(63,127,111) 337 | desaturated_cyan=Color(63,127,127) 338 | desaturated_sky=Color(63,111,127) 339 | desaturated_azure=Color(63,95,127) 340 | desaturated_blue=Color(63,63,127) 341 | desaturated_han=Color(79,63,127) 342 | desaturated_violet=Color(95,63,127) 343 | desaturated_purple=Color(111,63,127) 344 | desaturated_fuchsia=Color(127,63,127) 345 | desaturated_magenta=Color(127,63,111) 346 | desaturated_pink=Color(127,63,95) 347 | desaturated_crimson=Color(127,63,79) 348 | 349 | # metallic 350 | brass=Color(191,151,96) 351 | copper=Color(197,136,124) 352 | gold=Color(229,191,0) 353 | silver=Color(203,203,203) 354 | 355 | # miscellaneous 356 | celadon=Color(172,255,175) 357 | peach=Color(255,159,127) 358 | 359 | # color functions 360 | _lib.TCOD_color_lerp.restype = Color 361 | def color_lerp(c1, c2, a): 362 | return _lib.TCOD_color_lerp(c1, c2, c_float(a)) 363 | 364 | def color_set_hsv(c, h, s, v): 365 | _lib.TCOD_color_set_HSV(byref(c), c_float(h), c_float(s), c_float(v)) 366 | 367 | def color_get_hsv(c): 368 | h = c_float() 369 | s = c_float() 370 | v = c_float() 371 | _lib.TCOD_color_get_HSV(c, byref(h), byref(s), byref(v)) 372 | return h.value, s.value, v.value 373 | 374 | def color_scale_HSV(c, scoef, vcoef) : 375 | _lib.TCOD_color_scale_HSV(byref(c),c_float(scoef),c_float(vcoef)) 376 | 377 | def color_gen_map(colors, indexes): 378 | ccolors = (Color * len(colors))(*colors) 379 | cindexes = (c_int * len(indexes))(*indexes) 380 | cres = (Color * (max(indexes) + 1))() 381 | _lib.TCOD_color_gen_map(cres, len(colors), ccolors, cindexes) 382 | return cres 383 | 384 | ############################ 385 | # console module 386 | ############################ 387 | class Key(Structure): 388 | _fields_=[('vk', c_int), 389 | ('c', c_uint8), 390 | ('pressed', c_bool), 391 | ('lalt', c_bool), 392 | ('lctrl', c_bool), 393 | ('ralt', c_bool), 394 | ('rctrl', c_bool), 395 | ('shift', c_bool), 396 | ] 397 | 398 | class ConsoleBuffer: 399 | # simple console that allows direct (fast) access to cells. simplifies 400 | # use of the "fill" functions. 401 | def __init__(self, width, height, back_r=0, back_g=0, back_b=0, fore_r=0, fore_g=0, fore_b=0, char=' '): 402 | # initialize with given width and height. values to fill the buffer 403 | # are optional, defaults to black with no characters. 404 | n = width * height 405 | self.width = width 406 | self.height = height 407 | self.clear(back_r, back_g, back_b, fore_r, fore_g, fore_b, char) 408 | 409 | def clear(self, back_r=0, back_g=0, back_b=0, fore_r=0, fore_g=0, fore_b=0, char=' '): 410 | # clears the console. values to fill it with are optional, defaults 411 | # to black with no characters. 412 | n = self.width * self.height 413 | self.back_r = [back_r] * n 414 | self.back_g = [back_g] * n 415 | self.back_b = [back_b] * n 416 | self.fore_r = [fore_r] * n 417 | self.fore_g = [fore_g] * n 418 | self.fore_b = [fore_b] * n 419 | self.char = [ord(char)] * n 420 | 421 | def copy(self): 422 | # returns a copy of this ConsoleBuffer. 423 | other = ConsoleBuffer(0, 0) 424 | other.width = self.width 425 | other.height = self.height 426 | other.back_r = list(self.back_r) # make explicit copies of all lists 427 | other.back_g = list(self.back_g) 428 | other.back_b = list(self.back_b) 429 | other.fore_r = list(self.fore_r) 430 | other.fore_g = list(self.fore_g) 431 | other.fore_b = list(self.fore_b) 432 | other.char = list(self.char) 433 | return other 434 | 435 | def set_fore(self, x, y, r, g, b, char): 436 | # set the character and foreground color of one cell. 437 | i = self.width * y + x 438 | self.fore_r[i] = r 439 | self.fore_g[i] = g 440 | self.fore_b[i] = b 441 | self.char[i] = ord(char) 442 | 443 | def set_back(self, x, y, r, g, b): 444 | # set the background color of one cell. 445 | i = self.width * y + x 446 | self.back_r[i] = r 447 | self.back_g[i] = g 448 | self.back_b[i] = b 449 | 450 | def set(self, x, y, back_r, back_g, back_b, fore_r, fore_g, fore_b, char): 451 | # set the background color, foreground color and character of one cell. 452 | i = self.width * y + x 453 | self.back_r[i] = back_r 454 | self.back_g[i] = back_g 455 | self.back_b[i] = back_b 456 | self.fore_r[i] = fore_r 457 | self.fore_g[i] = fore_g 458 | self.fore_b[i] = fore_b 459 | self.char[i] = ord(char) 460 | 461 | def blit(self, dest, fill_fore=True, fill_back=True): 462 | # use libtcod's "fill" functions to write the buffer to a console. 463 | if (console_get_width(dest) != self.width or 464 | console_get_height(dest) != self.height): 465 | raise ValueError('ConsoleBuffer.blit: Destination console has an incorrect size.') 466 | 467 | s = struct.Struct('%di' % len(self.back_r)) 468 | 469 | if fill_back: 470 | _lib.TCOD_console_fill_background(dest, (c_int * len(self.back_r))(*self.back_r), (c_int * len(self.back_g))(*self.back_g), (c_int * len(self.back_b))(*self.back_b)) 471 | 472 | if fill_fore: 473 | _lib.TCOD_console_fill_foreground(dest, (c_int * len(self.fore_r))(*self.fore_r), (c_int * len(self.fore_g))(*self.fore_g), (c_int * len(self.fore_b))(*self.fore_b)) 474 | _lib.TCOD_console_fill_char(dest, (c_int * len(self.char))(*self.char)) 475 | 476 | _lib.TCOD_console_credits_render.restype = c_bool 477 | _lib.TCOD_console_is_fullscreen.restype = c_bool 478 | _lib.TCOD_console_is_window_closed.restype = c_bool 479 | _lib.TCOD_console_get_default_background.restype = Color 480 | _lib.TCOD_console_get_default_foreground.restype = Color 481 | _lib.TCOD_console_get_char_background.restype = Color 482 | _lib.TCOD_console_get_char_foreground.restype = Color 483 | _lib.TCOD_console_get_fading_color.restype = Color 484 | _lib.TCOD_console_is_key_pressed.restype = c_bool 485 | 486 | # background rendering modes 487 | BKGND_NONE = 0 488 | BKGND_SET = 1 489 | BKGND_MULTIPLY = 2 490 | BKGND_LIGHTEN = 3 491 | BKGND_DARKEN = 4 492 | BKGND_SCREEN = 5 493 | BKGND_COLOR_DODGE = 6 494 | BKGND_COLOR_BURN = 7 495 | BKGND_ADD = 8 496 | BKGND_ADDA = 9 497 | BKGND_BURN = 10 498 | BKGND_OVERLAY = 11 499 | BKGND_ALPH = 12 500 | BKGND_DEFAULT=13 501 | 502 | def BKGND_ALPHA(a): 503 | return BKGND_ALPH | (int(a * 255) << 8) 504 | 505 | def BKGND_ADDALPHA(a): 506 | return BKGND_ADDA | (int(a * 255) << 8) 507 | 508 | # non blocking key events types 509 | KEY_PRESSED = 1 510 | KEY_RELEASED = 2 511 | # key codes 512 | KEY_NONE = 0 513 | KEY_ESCAPE = 1 514 | KEY_BACKSPACE = 2 515 | KEY_TAB = 3 516 | KEY_ENTER = 4 517 | KEY_SHIFT = 5 518 | KEY_CONTROL = 6 519 | KEY_ALT = 7 520 | KEY_PAUSE = 8 521 | KEY_CAPSLOCK = 9 522 | KEY_PAGEUP = 10 523 | KEY_PAGEDOWN = 11 524 | KEY_END = 12 525 | KEY_HOME = 13 526 | KEY_UP = 14 527 | KEY_LEFT = 15 528 | KEY_RIGHT = 16 529 | KEY_DOWN = 17 530 | KEY_PRINTSCREEN = 18 531 | KEY_INSERT = 19 532 | KEY_DELETE = 20 533 | KEY_LWIN = 21 534 | KEY_RWIN = 22 535 | KEY_APPS = 23 536 | KEY_0 = 24 537 | KEY_1 = 25 538 | KEY_2 = 26 539 | KEY_3 = 27 540 | KEY_4 = 28 541 | KEY_5 = 29 542 | KEY_6 = 30 543 | KEY_7 = 31 544 | KEY_8 = 32 545 | KEY_9 = 33 546 | KEY_KP0 = 34 547 | KEY_KP1 = 35 548 | KEY_KP2 = 36 549 | KEY_KP3 = 37 550 | KEY_KP4 = 38 551 | KEY_KP5 = 39 552 | KEY_KP6 = 40 553 | KEY_KP7 = 41 554 | KEY_KP8 = 42 555 | KEY_KP9 = 43 556 | KEY_KPADD = 44 557 | KEY_KPSUB = 45 558 | KEY_KPDIV = 46 559 | KEY_KPMUL = 47 560 | KEY_KPDEC = 48 561 | KEY_KPENTER = 49 562 | KEY_F1 = 50 563 | KEY_F2 = 51 564 | KEY_F3 = 52 565 | KEY_F4 = 53 566 | KEY_F5 = 54 567 | KEY_F6 = 55 568 | KEY_F7 = 56 569 | KEY_F8 = 57 570 | KEY_F9 = 58 571 | KEY_F10 = 59 572 | KEY_F11 = 60 573 | KEY_F12 = 61 574 | KEY_NUMLOCK = 62 575 | KEY_SCROLLLOCK = 63 576 | KEY_SPACE = 64 577 | KEY_CHAR = 65 578 | # special chars 579 | # single walls 580 | CHAR_HLINE = 196 581 | CHAR_VLINE = 179 582 | CHAR_NE = 191 583 | CHAR_NW = 218 584 | CHAR_SE = 217 585 | CHAR_SW = 192 586 | CHAR_TEEW = 180 587 | CHAR_TEEE = 195 588 | CHAR_TEEN = 193 589 | CHAR_TEES = 194 590 | CHAR_CROSS = 197 591 | # double walls 592 | CHAR_DHLINE = 205 593 | CHAR_DVLINE = 186 594 | CHAR_DNE = 187 595 | CHAR_DNW = 201 596 | CHAR_DSE = 188 597 | CHAR_DSW = 200 598 | CHAR_DTEEW = 185 599 | CHAR_DTEEE = 204 600 | CHAR_DTEEN = 202 601 | CHAR_DTEES = 203 602 | CHAR_DCROSS = 206 603 | # blocks 604 | CHAR_BLOCK1 = 176 605 | CHAR_BLOCK2 = 177 606 | CHAR_BLOCK3 = 178 607 | # arrows 608 | CHAR_ARROW_N = 24 609 | CHAR_ARROW_S = 25 610 | CHAR_ARROW_E = 26 611 | CHAR_ARROW_W = 27 612 | # arrows without tail 613 | CHAR_ARROW2_N = 30 614 | CHAR_ARROW2_S = 31 615 | CHAR_ARROW2_E = 16 616 | CHAR_ARROW2_W = 17 617 | # double arrows 618 | CHAR_DARROW_H = 29 619 | CHAR_DARROW_V = 18 620 | # GUI stuff 621 | CHAR_CHECKBOX_UNSET = 224 622 | CHAR_CHECKBOX_SET = 225 623 | CHAR_RADIO_UNSET = 9 624 | CHAR_RADIO_SET = 10 625 | # sub-pixel resolution kit 626 | CHAR_SUBP_NW = 226 627 | CHAR_SUBP_NE = 227 628 | CHAR_SUBP_N = 228 629 | CHAR_SUBP_SE = 229 630 | CHAR_SUBP_DIAG = 230 631 | CHAR_SUBP_E = 231 632 | CHAR_SUBP_SW = 232 633 | # misc characters 634 | CHAR_BULLET = 7 635 | CHAR_BULLET_INV = 8 636 | CHAR_BULLET_SQUARE = 254 637 | CHAR_CENT = 189 638 | CHAR_CLUB = 5 639 | CHAR_COPYRIGHT = 184 640 | CHAR_CURRENCY = 207 641 | CHAR_DIAMOND = 4 642 | CHAR_DIVISION = 246 643 | CHAR_EXCLAM_DOUBLE = 19 644 | CHAR_FEMALE = 12 645 | CHAR_FUNCTION = 159 646 | CHAR_GRADE = 248 647 | CHAR_HALF = 171 648 | CHAR_HEART = 3 649 | CHAR_LIGHT = 15 650 | CHAR_MALE = 11 651 | CHAR_MULTIPLICATION = 158 652 | CHAR_NOTE = 13 653 | CHAR_NOTE_DOUBLE = 14 654 | CHAR_ONE_QUARTER = 172 655 | CHAR_PILCROW = 20 656 | CHAR_POUND = 156 657 | CHAR_POW1 = 251 658 | CHAR_POW2 = 253 659 | CHAR_POW3 = 252 660 | CHAR_RESERVED = 169 661 | CHAR_SECTION = 21 662 | CHAR_SMILIE = 1 663 | CHAR_SMILIE_INV = 2 664 | CHAR_SPADE = 6 665 | CHAR_THREE_QUARTERS = 243 666 | CHAR_UMLAUT = 249 667 | CHAR_YEN = 190 668 | # font flags 669 | FONT_LAYOUT_ASCII_INCOL = 1 670 | FONT_LAYOUT_ASCII_INROW = 2 671 | FONT_TYPE_GREYSCALE = 4 672 | FONT_TYPE_GRAYSCALE = 4 673 | FONT_LAYOUT_TCOD = 8 674 | # color control codes 675 | COLCTRL_1=1 676 | COLCTRL_2=2 677 | COLCTRL_3=3 678 | COLCTRL_4=4 679 | COLCTRL_5=5 680 | COLCTRL_NUMBER=5 681 | COLCTRL_FORE_RGB=6 682 | COLCTRL_BACK_RGB=7 683 | COLCTRL_STOP=8 684 | # renderers 685 | RENDERER_GLSL=0 686 | RENDERER_OPENGL=1 687 | RENDERER_SDL=2 688 | NB_RENDERERS=3 689 | # alignment 690 | LEFT=0 691 | RIGHT=1 692 | CENTER=2 693 | # initializing the console 694 | def console_init_root(w, h, title, fullscreen=False, renderer=RENDERER_SDL): 695 | _lib.TCOD_console_init_root(w, h, c_char_p(title), fullscreen, renderer) 696 | 697 | def console_get_width(con): 698 | return _lib.TCOD_console_get_width(con) 699 | 700 | def console_get_height(con): 701 | return _lib.TCOD_console_get_height(con) 702 | 703 | def console_set_custom_font(fontFile, flags=FONT_LAYOUT_ASCII_INCOL, nb_char_horiz=0, nb_char_vertic=0): 704 | _lib.TCOD_console_set_custom_font(c_char_p(fontFile), flags, nb_char_horiz, nb_char_vertic) 705 | 706 | def console_map_ascii_code_to_font(asciiCode, fontCharX, fontCharY): 707 | if type(asciiCode) == str or type(asciiCode) == bytes: 708 | _lib.TCOD_console_map_ascii_code_to_font(ord(asciiCode), fontCharX, 709 | fontCharY) 710 | else: 711 | _lib.TCOD_console_map_ascii_code_to_font(asciiCode, fontCharX, 712 | fontCharY) 713 | 714 | def console_map_ascii_codes_to_font(firstAsciiCode, nbCodes, fontCharX, 715 | fontCharY): 716 | if type(firstAsciiCode) == str or type(asciiCode) == bytes: 717 | _lib.TCOD_console_map_ascii_codes_to_font(ord(firstAsciiCode), nbCodes, 718 | fontCharX, fontCharY) 719 | else: 720 | _lib.TCOD_console_map_ascii_codes_to_font(firstAsciiCode, nbCodes, 721 | fontCharX, fontCharY) 722 | 723 | def console_map_string_to_font(s, fontCharX, fontCharY): 724 | if type(s) == bytes: 725 | _lib.TCOD_console_map_string_to_font(s, fontCharX, fontCharY) 726 | else: 727 | _lib.TCOD_console_map_string_to_font_utf(s, fontCharX, fontCharY) 728 | 729 | def console_is_fullscreen(): 730 | return _lib.TCOD_console_is_fullscreen() 731 | 732 | def console_set_fullscreen(fullscreen): 733 | _lib.TCOD_console_set_fullscreen(c_int(fullscreen)) 734 | 735 | def console_is_window_closed(): 736 | return _lib.TCOD_console_is_window_closed() 737 | 738 | def console_set_window_title(title): 739 | _lib.TCOD_console_set_window_title(c_char_p(title)) 740 | 741 | def console_credits(): 742 | _lib.TCOD_console_credits() 743 | 744 | def console_credits_reset(): 745 | _lib.TCOD_console_credits_reset() 746 | 747 | def console_credits_render(x, y, alpha): 748 | return _lib.TCOD_console_credits_render(x, y, c_int(alpha)) 749 | 750 | def console_flush(): 751 | _lib.TCOD_console_flush() 752 | 753 | # drawing on a console 754 | def console_set_default_background(con, col): 755 | _lib.TCOD_console_set_default_background(con, col) 756 | 757 | def console_set_default_foreground(con, col): 758 | _lib.TCOD_console_set_default_foreground(con, col) 759 | 760 | def console_clear(con): 761 | return _lib.TCOD_console_clear(con) 762 | 763 | def console_put_char(con, x, y, c, flag=BKGND_DEFAULT): 764 | if type(c) == str or type(c) == bytes: 765 | _lib.TCOD_console_put_char(con, x, y, ord(c), flag) 766 | else: 767 | _lib.TCOD_console_put_char(con, x, y, c, flag) 768 | 769 | def console_put_char_ex(con, x, y, c, fore, back): 770 | if type(c) == str or type(c) == bytes: 771 | _lib.TCOD_console_put_char_ex(con, x, y, ord(c), fore, back) 772 | else: 773 | _lib.TCOD_console_put_char_ex(con, x, y, c, fore, back) 774 | 775 | def console_set_char_background(con, x, y, col, flag=BKGND_SET): 776 | _lib.TCOD_console_set_char_background(con, x, y, col, flag) 777 | 778 | def console_set_char_foreground(con, x, y, col): 779 | _lib.TCOD_console_set_char_foreground(con, x, y, col) 780 | 781 | def console_set_char(con, x, y, c): 782 | if type(c) == str or type(c) == bytes: 783 | _lib.TCOD_console_set_char(con, x, y, ord(c)) 784 | else: 785 | _lib.TCOD_console_set_char(con, x, y, c) 786 | 787 | def console_set_background_flag(con, flag): 788 | _lib.TCOD_console_set_background_flag(con, c_int(flag)) 789 | 790 | def console_get_background_flag(con): 791 | return _lib.TCOD_console_get_background_flag(con) 792 | 793 | def console_set_alignment(con, alignment): 794 | _lib.TCOD_console_set_alignment(con, c_int(alignment)) 795 | 796 | def console_get_alignment(con): 797 | return _lib.TCOD_console_get_alignment(con) 798 | 799 | def console_print(con, x, y, fmt): 800 | if type(fmt) == bytes: 801 | _lib.TCOD_console_print(c_void_p(con), x, y, c_char_p(fmt)) 802 | else: 803 | _lib.TCOD_console_print_utf(c_void_p(con), x, y, fmt) 804 | 805 | def console_print_ex(con, x, y, flag, alignment, fmt): 806 | if type(fmt) == bytes: 807 | _lib.TCOD_console_print_ex(c_void_p(con), x, y, flag, alignment, c_char_p(fmt)) 808 | else: 809 | _lib.TCOD_console_print_ex_utf(c_void_p(con), x, y, flag, alignment, fmt) 810 | 811 | def console_print_rect(con, x, y, w, h, fmt): 812 | if type(fmt) == bytes: 813 | return _lib.TCOD_console_print_rect(c_void_p(con), x, y, w, h, c_char_p(fmt)) 814 | else: 815 | return _lib.TCOD_console_print_rect_utf(c_void_p(con), x, y, w, h, fmt) 816 | 817 | def console_print_rect_ex(con, x, y, w, h, flag, alignment, fmt): 818 | if type(fmt) == bytes: 819 | return _lib.TCOD_console_print_rect_ex(c_void_p(con), x, y, w, h, flag, alignment, c_char_p(fmt)) 820 | else: 821 | return _lib.TCOD_console_print_rect_ex_utf(c_void_p(con), x, y, w, h, flag, alignment, fmt) 822 | 823 | def console_get_height_rect(con, x, y, w, h, fmt): 824 | if type(fmt) == bytes: 825 | return _lib.TCOD_console_get_height_rect(c_void_p(con), x, y, w, h, c_char_p(fmt)) 826 | else: 827 | return _lib.TCOD_console_get_height_rect_utf(c_void_p(con), x, y, w, h, fmt) 828 | 829 | def console_rect(con, x, y, w, h, clr, flag=BKGND_DEFAULT): 830 | _lib.TCOD_console_rect(con, x, y, w, h, c_int(clr), flag) 831 | 832 | def console_hline(con, x, y, l, flag=BKGND_DEFAULT): 833 | _lib.TCOD_console_hline( con, x, y, l, flag) 834 | 835 | def console_vline(con, x, y, l, flag=BKGND_DEFAULT): 836 | _lib.TCOD_console_vline( con, x, y, l, flag) 837 | 838 | def console_print_frame(con, x, y, w, h, clear=True, flag=BKGND_DEFAULT, fmt=0): 839 | _lib.TCOD_console_print_frame(c_void_p(con), x, y, w, h, c_int(clear), flag, c_char_p(fmt)) 840 | 841 | def console_set_color_control(con,fore,back) : 842 | _lib.TCOD_console_set_color_control(con,fore,back) 843 | 844 | def console_get_default_background(con): 845 | return _lib.TCOD_console_get_default_background(con) 846 | 847 | def console_get_default_foreground(con): 848 | return _lib.TCOD_console_get_default_foreground(con) 849 | 850 | def console_get_char_background(con, x, y): 851 | return _lib.TCOD_console_get_char_background(con, x, y) 852 | 853 | def console_get_char_foreground(con, x, y): 854 | return _lib.TCOD_console_get_char_foreground(con, x, y) 855 | 856 | def console_get_char(con, x, y): 857 | return _lib.TCOD_console_get_char(con, x, y) 858 | 859 | def console_set_fade(fade, fadingColor): 860 | _lib.TCOD_console_set_fade(fade, fadingColor) 861 | ##_lib.TCOD_console_set_fade_wrapper(fade, fadingColor) 862 | 863 | def console_get_fade(): 864 | return _lib.TCOD_console_get_fade().value 865 | 866 | def console_get_fading_color(): 867 | return _lib.TCOD_console_get_fading_color() 868 | 869 | # handling keyboard input 870 | def console_wait_for_keypress(flush): 871 | k=Key() 872 | _lib.TCOD_console_wait_for_keypress_wrapper(byref(k),c_bool(flush)) 873 | return k 874 | 875 | def console_check_for_keypress(flags=KEY_RELEASED): 876 | k=Key() 877 | _lib.TCOD_console_check_for_keypress_wrapper(byref(k),c_int(flags)) 878 | return k 879 | 880 | def console_is_key_pressed(key): 881 | return _lib.TCOD_console_is_key_pressed(key) 882 | 883 | def console_set_keyboard_repeat(initial_delay, interval): 884 | _lib.TCOD_console_set_keyboard_repeat(initial_delay, interval) 885 | 886 | def console_disable_keyboard_repeat(): 887 | _lib.TCOD_console_disable_keyboard_repeat() 888 | 889 | # using offscreen consoles 890 | def console_new(w, h): 891 | return _lib.TCOD_console_new(w, h) 892 | def console_from_file(filename): 893 | return _lib.TCOD_console_from_file(filename) 894 | def console_get_width(con): 895 | return _lib.TCOD_console_get_width(con) 896 | 897 | def console_get_height(con): 898 | return _lib.TCOD_console_get_height(con) 899 | 900 | def console_blit(src, x, y, w, h, dst, xdst, ydst, ffade=1.0,bfade=1.0): 901 | _lib.TCOD_console_blit(src, x, y, w, h, dst, xdst, ydst, c_float(ffade), c_float(bfade)) 902 | 903 | def console_set_key_color(con, col): 904 | _lib.TCOD_console_set_key_color(con, col) 905 | 906 | def console_delete(con): 907 | _lib.TCOD_console_delete(con) 908 | 909 | # fast color filling 910 | def console_fill_foreground(con,r,g,b) : 911 | if len(r) != len(g) or len(r) != len(b): 912 | raise TypeError('R, G and B must all have the same size.') 913 | 914 | if (numpy_available and isinstance(r, numpy.ndarray) and 915 | isinstance(g, numpy.ndarray) and isinstance(b, numpy.ndarray)): 916 | #numpy arrays, use numpy's ctypes functions 917 | r = numpy.ascontiguousarray(r, dtype=numpy.int_) 918 | g = numpy.ascontiguousarray(g, dtype=numpy.int_) 919 | b = numpy.ascontiguousarray(b, dtype=numpy.int_) 920 | cr = r.ctypes.data_as(POINTER(c_int)) 921 | cg = g.ctypes.data_as(POINTER(c_int)) 922 | cb = b.ctypes.data_as(POINTER(c_int)) 923 | else: 924 | # otherwise convert using ctypes arrays 925 | cr = (c_int * len(r))(*r) 926 | cg = (c_int * len(g))(*g) 927 | cb = (c_int * len(b))(*b) 928 | 929 | _lib.TCOD_console_fill_foreground(con, cr, cg, cb) 930 | 931 | def console_fill_background(con,r,g,b) : 932 | if len(r) != len(g) or len(r) != len(b): 933 | raise TypeError('R, G and B must all have the same size.') 934 | 935 | if (numpy_available and isinstance(r, numpy.ndarray) and 936 | isinstance(g, numpy.ndarray) and isinstance(b, numpy.ndarray)): 937 | #numpy arrays, use numpy's ctypes functions 938 | r = numpy.ascontiguousarray(r, dtype=numpy.int_) 939 | g = numpy.ascontiguousarray(g, dtype=numpy.int_) 940 | b = numpy.ascontiguousarray(b, dtype=numpy.int_) 941 | cr = r.ctypes.data_as(POINTER(c_int)) 942 | cg = g.ctypes.data_as(POINTER(c_int)) 943 | cb = b.ctypes.data_as(POINTER(c_int)) 944 | else: 945 | # otherwise convert using ctypes arrays 946 | cr = (c_int * len(r))(*r) 947 | cg = (c_int * len(g))(*g) 948 | cb = (c_int * len(b))(*b) 949 | 950 | _lib.TCOD_console_fill_background(con, cr, cg, cb) 951 | 952 | def console_fill_char(con,arr) : 953 | if (numpy_available and isinstance(arr, numpy.ndarray) ): 954 | #numpy arrays, use numpy's ctypes functions 955 | arr = numpy.ascontiguousarray(arr, dtype=numpy.int_) 956 | carr = arr.ctypes.data_as(POINTER(c_int)) 957 | else: 958 | #otherwise convert using the struct module 959 | carr = struct.pack('%di' % len(arr), *arr) 960 | 961 | _lib.TCOD_console_fill_char(con, carr) 962 | 963 | def console_load_asc(con, filename) : 964 | _lib.TCOD_console_load_asc(con,filename) 965 | def console_save_asc(con, filename) : 966 | _lib.TCOD_console_save_asc(con,filename) 967 | def console_load_apf(con, filename) : 968 | _lib.TCOD_console_load_apf(con,filename) 969 | def console_save_apf(con, filename) : 970 | _lib.TCOD_console_save_apf(con,filename) 971 | 972 | ############################ 973 | # sys module 974 | ############################ 975 | _lib.TCOD_sys_get_last_frame_length.restype = c_float 976 | _lib.TCOD_sys_elapsed_seconds.restype = c_float 977 | 978 | # high precision time functions 979 | def sys_set_fps(fps): 980 | _lib.TCOD_sys_set_fps(fps) 981 | 982 | def sys_get_fps(): 983 | return _lib.TCOD_sys_get_fps() 984 | 985 | def sys_get_last_frame_length(): 986 | return _lib.TCOD_sys_get_last_frame_length() 987 | 988 | def sys_sleep_milli(val): 989 | _lib.TCOD_sys_sleep_milli(c_uint(val)) 990 | 991 | def sys_elapsed_milli(): 992 | return _lib.TCOD_sys_elapsed_milli() 993 | 994 | def sys_elapsed_seconds(): 995 | return _lib.TCOD_sys_elapsed_seconds() 996 | 997 | def sys_set_renderer(renderer): 998 | _lib.TCOD_sys_set_renderer(renderer) 999 | 1000 | def sys_get_renderer(): 1001 | return _lib.TCOD_sys_get_renderer() 1002 | 1003 | # easy screenshots 1004 | def sys_save_screenshot(name=0): 1005 | _lib.TCOD_sys_save_screenshot(c_char_p(name)) 1006 | 1007 | # custom fullscreen resolution 1008 | def sys_force_fullscreen_resolution(width, height): 1009 | _lib.TCOD_sys_force_fullscreen_resolution(width, height) 1010 | 1011 | def sys_get_current_resolution(): 1012 | w = c_int() 1013 | h = c_int() 1014 | _lib.TCOD_sys_get_current_resolution(byref(w), byref(h)) 1015 | return w.value, h.value 1016 | 1017 | def sys_get_char_size(): 1018 | w = c_int() 1019 | h = c_int() 1020 | _lib.TCOD_sys_get_char_size(byref(w), byref(h)) 1021 | return w.value, h.value 1022 | 1023 | # update font bitmap 1024 | def sys_update_char(asciiCode, fontx, fonty, img, x, y) : 1025 | _lib.TCOD_sys_update_char(c_int(asciiCode),c_int(fontx),c_int(fonty),img,c_int(x),c_int(y)) 1026 | 1027 | # custom SDL post renderer 1028 | SDL_RENDERER_FUNC = CFUNCTYPE(None, c_void_p) 1029 | def sys_register_SDL_renderer(callback): 1030 | global sdl_renderer_func 1031 | sdl_renderer_func = SDL_RENDERER_FUNC(callback) 1032 | _lib.TCOD_sys_register_SDL_renderer(sdl_renderer_func) 1033 | 1034 | # events 1035 | EVENT_KEY_PRESS=1 1036 | EVENT_KEY_RELEASE=2 1037 | EVENT_KEY=EVENT_KEY_PRESS|EVENT_KEY_RELEASE 1038 | EVENT_MOUSE_MOVE=4 1039 | EVENT_MOUSE_PRESS=8 1040 | EVENT_MOUSE_RELEASE=16 1041 | EVENT_MOUSE=EVENT_MOUSE_MOVE|EVENT_MOUSE_PRESS|EVENT_MOUSE_RELEASE 1042 | EVENT_ANY=EVENT_KEY|EVENT_MOUSE 1043 | def sys_check_for_event(mask,k,m) : 1044 | return _lib.TCOD_sys_check_for_event(c_int(mask),byref(k),byref(m)) 1045 | 1046 | def sys_wait_for_event(mask,k,m,flush) : 1047 | return _lib.TCOD_sys_wait_for_event(c_int(mask),byref(k),byref(m),c_bool(flush)) 1048 | 1049 | ############################ 1050 | # line module 1051 | ############################ 1052 | _lib.TCOD_line_step.restype = c_bool 1053 | _lib.TCOD_line.restype=c_bool 1054 | _lib.TCOD_line_step_mt.restype = c_bool 1055 | 1056 | def line_init(xo, yo, xd, yd): 1057 | _lib.TCOD_line_init(xo, yo, xd, yd) 1058 | 1059 | def line_step(): 1060 | x = c_int() 1061 | y = c_int() 1062 | ret = _lib.TCOD_line_step(byref(x), byref(y)) 1063 | if not ret: 1064 | return x.value, y.value 1065 | return None,None 1066 | 1067 | def line(xo,yo,xd,yd,py_callback) : 1068 | LINE_CBK_FUNC=CFUNCTYPE(c_bool,c_int,c_int) 1069 | c_callback=LINE_CBK_FUNC(py_callback) 1070 | return _lib.TCOD_line(xo,yo,xd,yd,c_callback) 1071 | 1072 | def line_iter(xo, yo, xd, yd): 1073 | data = (c_int * 9)() # struct TCOD_bresenham_data_t 1074 | _lib.TCOD_line_init_mt(xo, yo, xd, yd, data) 1075 | x = c_int(xo) 1076 | y = c_int(yo) 1077 | done = False 1078 | while not done: 1079 | yield x.value, y.value 1080 | done = _lib.TCOD_line_step_mt(byref(x), byref(y), data) 1081 | 1082 | ############################ 1083 | # image module 1084 | ############################ 1085 | _lib.TCOD_image_is_pixel_transparent.restype = c_bool 1086 | _lib.TCOD_image_get_pixel.restype = Color 1087 | _lib.TCOD_image_get_mipmap_pixel.restype = Color 1088 | 1089 | def image_new(width, height): 1090 | return _lib.TCOD_image_new(width, height) 1091 | 1092 | def image_clear(image,col) : 1093 | _lib.TCOD_image_clear(image,col) 1094 | 1095 | def image_invert(image) : 1096 | _lib.TCOD_image_invert(image) 1097 | 1098 | def image_hflip(image) : 1099 | _lib.TCOD_image_hflip(image) 1100 | 1101 | def image_rotate90(image, num=1) : 1102 | _lib.TCOD_image_rotate90(image,num) 1103 | 1104 | def image_vflip(image) : 1105 | _lib.TCOD_image_vflip(image) 1106 | 1107 | def image_scale(image, neww, newh) : 1108 | _lib.TCOD_image_scale(image,c_int(neww),c_int(newh)) 1109 | 1110 | def image_set_key_color(image,col) : 1111 | _lib.TCOD_image_set_key_color(image,col) 1112 | 1113 | def image_get_alpha(image,x,y) : 1114 | return _lib.TCOD_image_get_alpha(image,c_int(x),c_int(y)) 1115 | 1116 | def image_is_pixel_transparent(image,x,y) : 1117 | return _lib.TCOD_image_is_pixel_transparent(image,c_int(x),c_int(y)) 1118 | 1119 | def image_load(filename): 1120 | return _lib.TCOD_image_load(c_char_p(filename)) 1121 | 1122 | def image_from_console(console): 1123 | return _lib.TCOD_image_from_console(console) 1124 | 1125 | def image_refresh_console(image, console): 1126 | _lib.TCOD_image_refresh_console(image, console) 1127 | 1128 | def image_get_size(image): 1129 | w=c_int() 1130 | h=c_int() 1131 | _lib.TCOD_image_get_size(image, byref(w), byref(h)) 1132 | return w.value, h.value 1133 | 1134 | def image_get_pixel(image, x, y): 1135 | return _lib.TCOD_image_get_pixel(image, x, y) 1136 | 1137 | def image_get_mipmap_pixel(image, x0, y0, x1, y1): 1138 | return _lib.TCOD_image_get_mipmap_pixel(image, c_float(x0), c_float(y0), 1139 | c_float(x1), c_float(y1)) 1140 | def image_put_pixel(image, x, y, col): 1141 | _lib.TCOD_image_put_pixel(image, x, y, col) 1142 | ##_lib.TCOD_image_put_pixel_wrapper(image, x, y, col) 1143 | 1144 | def image_blit(image, console, x, y, bkgnd_flag, scalex, scaley, angle): 1145 | _lib.TCOD_image_blit(image, console, c_float(x), c_float(y), bkgnd_flag, 1146 | c_float(scalex), c_float(scaley), c_float(angle)) 1147 | 1148 | def image_blit_rect(image, console, x, y, w, h, bkgnd_flag): 1149 | _lib.TCOD_image_blit_rect(image, console, x, y, w, h, bkgnd_flag) 1150 | 1151 | def image_blit_2x(image, console, dx, dy, sx=0, sy=0, w=-1, h=-1): 1152 | _lib.TCOD_image_blit_2x(image, console, dx,dy,sx,sy,w,h) 1153 | 1154 | def image_save(image, filename): 1155 | _lib.TCOD_image_save(image, c_char_p(filename)) 1156 | 1157 | def image_delete(image): 1158 | _lib.TCOD_image_delete(image) 1159 | 1160 | ############################ 1161 | # mouse module 1162 | ############################ 1163 | class Mouse(Structure): 1164 | _fields_=[('x', c_int), 1165 | ('y', c_int), 1166 | ('dx', c_int), 1167 | ('dy', c_int), 1168 | ('cx', c_int), 1169 | ('cy', c_int), 1170 | ('dcx', c_int), 1171 | ('dcy', c_int), 1172 | ('lbutton', c_bool), 1173 | ('rbutton', c_bool), 1174 | ('mbutton', c_bool), 1175 | ('lbutton_pressed', c_bool), 1176 | ('rbutton_pressed', c_bool), 1177 | ('mbutton_pressed', c_bool), 1178 | ('wheel_up', c_bool), 1179 | ('wheel_down', c_bool), 1180 | ] 1181 | 1182 | _lib.TCOD_mouse_is_cursor_visible.restype = c_bool 1183 | 1184 | def mouse_show_cursor(visible): 1185 | _lib.TCOD_mouse_show_cursor(c_int(visible)) 1186 | 1187 | def mouse_is_cursor_visible(): 1188 | return _lib.TCOD_mouse_is_cursor_visible() 1189 | 1190 | def mouse_move(x, y): 1191 | _lib.TCOD_mouse_move(x, y) 1192 | 1193 | def mouse_get_status(): 1194 | mouse=Mouse() 1195 | _lib.TCOD_mouse_get_status_wrapper(byref(mouse)) 1196 | return mouse 1197 | 1198 | ############################ 1199 | # parser module 1200 | ############################ 1201 | _lib.TCOD_struct_get_name.restype = c_char_p 1202 | _lib.TCOD_struct_is_mandatory.restype = c_bool 1203 | _lib.TCOD_parser_get_bool_property.restype = c_bool 1204 | _lib.TCOD_parser_get_float_property.restype = c_float 1205 | _lib.TCOD_parser_get_string_property.restype = c_char_p 1206 | _lib.TCOD_parser_get_color_property.restype = Color 1207 | 1208 | class Dice(Structure): 1209 | _fields_=[('nb_dices', c_int), 1210 | ('nb_faces', c_int), 1211 | ('multiplier', c_float), 1212 | ('addsub', c_float), 1213 | ] 1214 | 1215 | def __repr__(self): 1216 | return "Dice(%d, %d, %s, %s)" % (self.nb_dices, self.nb_faces, 1217 | self.multiplier, self.addsub) 1218 | 1219 | class _CValue(Union): 1220 | _fields_=[('c',c_uint8), 1221 | ('i',c_int), 1222 | ('f',c_float), 1223 | ('s',c_char_p), 1224 | # JBR03192012 See http://bugs.python.org/issue14354 for why these are not defined as their actual types 1225 | ('col',c_uint8 * 3), 1226 | ('dice',c_int * 4), 1227 | ('custom',c_void_p), 1228 | ] 1229 | 1230 | _CFUNC_NEW_STRUCT = CFUNCTYPE(c_uint, c_void_p, c_char_p) 1231 | _CFUNC_NEW_FLAG = CFUNCTYPE(c_uint, c_char_p) 1232 | _CFUNC_NEW_PROPERTY = CFUNCTYPE(c_uint, c_char_p, c_int, _CValue) 1233 | 1234 | class _CParserListener(Structure): 1235 | _fields_=[('new_struct', _CFUNC_NEW_STRUCT), 1236 | ('new_flag',_CFUNC_NEW_FLAG), 1237 | ('new_property',_CFUNC_NEW_PROPERTY), 1238 | ('end_struct',_CFUNC_NEW_STRUCT), 1239 | ('error',_CFUNC_NEW_FLAG), 1240 | ] 1241 | 1242 | # property types 1243 | TYPE_NONE = 0 1244 | TYPE_BOOL = 1 1245 | TYPE_CHAR = 2 1246 | TYPE_INT = 3 1247 | TYPE_FLOAT = 4 1248 | TYPE_STRING = 5 1249 | TYPE_COLOR = 6 1250 | TYPE_DICE = 7 1251 | TYPE_VALUELIST00 = 8 1252 | TYPE_VALUELIST01 = 9 1253 | TYPE_VALUELIST02 = 10 1254 | TYPE_VALUELIST03 = 11 1255 | TYPE_VALUELIST04 = 12 1256 | TYPE_VALUELIST05 = 13 1257 | TYPE_VALUELIST06 = 14 1258 | TYPE_VALUELIST07 = 15 1259 | TYPE_VALUELIST08 = 16 1260 | TYPE_VALUELIST09 = 17 1261 | TYPE_VALUELIST10 = 18 1262 | TYPE_VALUELIST11 = 19 1263 | TYPE_VALUELIST12 = 20 1264 | TYPE_VALUELIST13 = 21 1265 | TYPE_VALUELIST14 = 22 1266 | TYPE_VALUELIST15 = 23 1267 | TYPE_LIST = 1024 1268 | 1269 | def _convert_TCODList(clist, typ): 1270 | res = list() 1271 | for i in range(_lib.TCOD_list_size(clist)): 1272 | elt = _lib.TCOD_list_get(clist, i) 1273 | elt = cast(elt, c_void_p) 1274 | if typ == TYPE_BOOL: 1275 | elt = c_bool.from_buffer(elt).value 1276 | elif typ == TYPE_CHAR: 1277 | elt = c_char.from_buffer(elt).value 1278 | elif typ == TYPE_INT: 1279 | elt = c_int.from_buffer(elt).value 1280 | elif typ == TYPE_FLOAT: 1281 | elt = c_float.from_buffer(elt).value 1282 | elif typ == TYPE_STRING or TYPE_VALUELIST15 >= typ >= TYPE_VALUELIST00: 1283 | elt = cast(elt, c_char_p).value 1284 | elif typ == TYPE_COLOR: 1285 | elt = Color.from_buffer_copy(elt) 1286 | elif typ == TYPE_DICE: 1287 | # doesn't work 1288 | elt = Dice.from_buffer_copy(elt) 1289 | res.append(elt) 1290 | return res 1291 | 1292 | def parser_new(): 1293 | return _lib.TCOD_parser_new() 1294 | 1295 | def parser_new_struct(parser, name): 1296 | return _lib.TCOD_parser_new_struct(parser, name) 1297 | 1298 | def struct_add_flag(struct, name): 1299 | _lib.TCOD_struct_add_flag(struct, name) 1300 | 1301 | def struct_add_property(struct, name, typ, mandatory): 1302 | _lib.TCOD_struct_add_property(struct, name, typ, c_bool(mandatory)) 1303 | 1304 | def struct_add_value_list(struct, name, value_list, mandatory): 1305 | CARRAY = c_char_p * (len(value_list) + 1) 1306 | cvalue_list = CARRAY() 1307 | for i in range(len(value_list)): 1308 | cvalue_list[i] = cast(value_list[i], c_char_p) 1309 | cvalue_list[len(value_list)] = 0 1310 | _lib.TCOD_struct_add_value_list(struct, name, cvalue_list, c_bool(mandatory)) 1311 | 1312 | def struct_add_list_property(struct, name, typ, mandatory): 1313 | _lib.TCOD_struct_add_list_property(struct, name, typ, c_bool(mandatory)) 1314 | 1315 | def struct_add_structure(struct, sub_struct): 1316 | _lib.TCOD_struct_add_structure(struct, sub_struct) 1317 | 1318 | def struct_get_name(struct): 1319 | return _lib.TCOD_struct_get_name(struct) 1320 | 1321 | def struct_is_mandatory(struct, name): 1322 | return _lib.TCOD_struct_is_mandatory(struct, name) 1323 | 1324 | def struct_get_type(struct, name): 1325 | return _lib.TCOD_struct_get_type(struct, name) 1326 | 1327 | def parser_run(parser, filename, listener=0): 1328 | if listener != 0: 1329 | clistener=_CParserListener() 1330 | def value_converter(name, typ, value): 1331 | if typ == TYPE_BOOL: 1332 | return listener.new_property(name, typ, value.c == 1) 1333 | elif typ == TYPE_CHAR: 1334 | return listener.new_property(name, typ, '%c' % (value.c & 0xFF)) 1335 | elif typ == TYPE_INT: 1336 | return listener.new_property(name, typ, value.i) 1337 | elif typ == TYPE_FLOAT: 1338 | return listener.new_property(name, typ, value.f) 1339 | elif typ == TYPE_STRING or \ 1340 | TYPE_VALUELIST15 >= typ >= TYPE_VALUELIST00: 1341 | return listener.new_property(name, typ, value.s) 1342 | elif typ == TYPE_COLOR: 1343 | col = cast(value.col, POINTER(Color)).contents 1344 | return listener.new_property(name, typ, col) 1345 | elif typ == TYPE_DICE: 1346 | dice = cast(value.dice, POINTER(Dice)).contents 1347 | return listener.new_property(name, typ, dice) 1348 | elif typ & TYPE_LIST: 1349 | return listener.new_property(name, typ, 1350 | _convert_TCODList(value.custom, typ & 0xFF)) 1351 | return True 1352 | clistener.new_struct = _CFUNC_NEW_STRUCT(listener.new_struct) 1353 | clistener.new_flag = _CFUNC_NEW_FLAG(listener.new_flag) 1354 | clistener.new_property = _CFUNC_NEW_PROPERTY(value_converter) 1355 | clistener.end_struct = _CFUNC_NEW_STRUCT(listener.end_struct) 1356 | clistener.error = _CFUNC_NEW_FLAG(listener.error) 1357 | _lib.TCOD_parser_run(parser, c_char_p(filename), byref(clistener)) 1358 | else: 1359 | _lib.TCOD_parser_run(parser, c_char_p(filename), 0) 1360 | 1361 | def parser_delete(parser): 1362 | _lib.TCOD_parser_delete(parser) 1363 | 1364 | def parser_get_bool_property(parser, name): 1365 | return _lib.TCOD_parser_get_bool_property(parser, c_char_p(name)) 1366 | 1367 | def parser_get_int_property(parser, name): 1368 | return _lib.TCOD_parser_get_int_property(parser, c_char_p(name)) 1369 | 1370 | def parser_get_char_property(parser, name): 1371 | return '%c' % _lib.TCOD_parser_get_char_property(parser, c_char_p(name)) 1372 | 1373 | def parser_get_float_property(parser, name): 1374 | return _lib.TCOD_parser_get_float_property(parser, c_char_p(name)) 1375 | 1376 | def parser_get_string_property(parser, name): 1377 | return _lib.TCOD_parser_get_string_property(parser, c_char_p(name)) 1378 | 1379 | def parser_get_color_property(parser, name): 1380 | return _lib.TCOD_parser_get_color_property(parser, c_char_p(name)) 1381 | 1382 | def parser_get_dice_property(parser, name): 1383 | d = Dice() 1384 | _lib.TCOD_parser_get_dice_property_py(c_void_p(parser), c_char_p(name), byref(d)) 1385 | return d 1386 | 1387 | def parser_get_list_property(parser, name, typ): 1388 | clist = _lib.TCOD_parser_get_list_property(parser, c_char_p(name), c_int(typ)) 1389 | return _convert_TCODList(clist, typ) 1390 | 1391 | ############################ 1392 | # random module 1393 | ############################ 1394 | _lib.TCOD_random_get_float.restype = c_float 1395 | _lib.TCOD_random_get_double.restype = c_double 1396 | 1397 | RNG_MT = 0 1398 | RNG_CMWC = 1 1399 | 1400 | DISTRIBUTION_LINEAR = 0 1401 | DISTRIBUTION_GAUSSIAN = 1 1402 | DISTRIBUTION_GAUSSIAN_RANGE = 2 1403 | DISTRIBUTION_GAUSSIAN_INVERSE = 3 1404 | DISTRIBUTION_GAUSSIAN_RANGE_INVERSE = 4 1405 | 1406 | def random_get_instance(): 1407 | return _lib.TCOD_random_get_instance() 1408 | 1409 | def random_new(algo=RNG_CMWC): 1410 | return _lib.TCOD_random_new(algo) 1411 | 1412 | def random_new_from_seed(seed, algo=RNG_CMWC): 1413 | return _lib.TCOD_random_new_from_seed(algo,c_uint(seed)) 1414 | 1415 | def random_set_distribution(rnd, dist) : 1416 | _lib.TCOD_random_set_distribution(rnd, dist) 1417 | 1418 | def random_get_int(rnd, mi, ma): 1419 | return _lib.TCOD_random_get_int(rnd, mi, ma) 1420 | 1421 | def random_get_float(rnd, mi, ma): 1422 | return _lib.TCOD_random_get_float(rnd, c_float(mi), c_float(ma)) 1423 | 1424 | def random_get_double(rnd, mi, ma): 1425 | return _lib.TCOD_random_get_double(rnd, c_double(mi), c_double(ma)) 1426 | 1427 | def random_get_int_mean(rnd, mi, ma, mean): 1428 | return _lib.TCOD_random_get_int_mean(rnd, mi, ma, mean) 1429 | 1430 | def random_get_float_mean(rnd, mi, ma, mean): 1431 | return _lib.TCOD_random_get_float_mean(rnd, c_float(mi), c_float(ma), c_float(mean)) 1432 | 1433 | def random_get_double_mean(rnd, mi, ma, mean): 1434 | return _lib.TCOD_random_get_double_mean(rnd, c_double(mi), c_double(ma), c_double(mean)) 1435 | 1436 | def random_save(rnd): 1437 | return _lib.TCOD_random_save(rnd) 1438 | 1439 | def random_restore(rnd, backup): 1440 | _lib.TCOD_random_restore(rnd, backup) 1441 | 1442 | def random_delete(rnd): 1443 | _lib.TCOD_random_delete(rnd) 1444 | 1445 | ############################ 1446 | # noise module 1447 | ############################ 1448 | _lib.TCOD_noise_get.restype = c_float 1449 | _lib.TCOD_noise_get_ex.restype = c_float 1450 | _lib.TCOD_noise_get_fbm.restype = c_float 1451 | _lib.TCOD_noise_get_fbm_ex.restype = c_float 1452 | _lib.TCOD_noise_get_turbulence.restype = c_float 1453 | _lib.TCOD_noise_get_turbulence_ex.restype = c_float 1454 | 1455 | NOISE_DEFAULT_HURST = 0.5 1456 | NOISE_DEFAULT_LACUNARITY = 2.0 1457 | 1458 | NOISE_DEFAULT = 0 1459 | NOISE_PERLIN = 1 1460 | NOISE_SIMPLEX = 2 1461 | NOISE_WAVELET = 4 1462 | 1463 | _NOISE_PACKER_FUNC = (None, 1464 | (c_float * 1), 1465 | (c_float * 2), 1466 | (c_float * 3), 1467 | (c_float * 4), 1468 | ) 1469 | 1470 | def noise_new(dim, h=NOISE_DEFAULT_HURST, l=NOISE_DEFAULT_LACUNARITY, random=0): 1471 | return _lib.TCOD_noise_new(dim, c_float(h), c_float(l), random) 1472 | 1473 | def noise_set_type(n, typ) : 1474 | _lib.TCOD_noise_set_type(n,typ) 1475 | 1476 | def noise_get(n, f, typ=NOISE_DEFAULT): 1477 | return _lib.TCOD_noise_get_ex(n, _NOISE_PACKER_FUNC[len(f)](*f), typ) 1478 | 1479 | def noise_get_fbm(n, f, oc, typ=NOISE_DEFAULT): 1480 | return _lib.TCOD_noise_get_fbm_ex(n, _NOISE_PACKER_FUNC[len(f)](*f), c_float(oc), typ) 1481 | 1482 | def noise_get_turbulence(n, f, oc, typ=NOISE_DEFAULT): 1483 | return _lib.TCOD_noise_get_turbulence_ex(n, _NOISE_PACKER_FUNC[len(f)](*f), c_float(oc), typ) 1484 | 1485 | def noise_delete(n): 1486 | _lib.TCOD_noise_delete(n) 1487 | 1488 | ############################ 1489 | # fov module 1490 | ############################ 1491 | _lib.TCOD_map_is_in_fov.restype = c_bool 1492 | _lib.TCOD_map_is_transparent.restype = c_bool 1493 | _lib.TCOD_map_is_walkable.restype = c_bool 1494 | 1495 | FOV_BASIC = 0 1496 | FOV_DIAMOND = 1 1497 | FOV_SHADOW = 2 1498 | FOV_PERMISSIVE_0 = 3 1499 | FOV_PERMISSIVE_1 = 4 1500 | FOV_PERMISSIVE_2 = 5 1501 | FOV_PERMISSIVE_3 = 6 1502 | FOV_PERMISSIVE_4 = 7 1503 | FOV_PERMISSIVE_5 = 8 1504 | FOV_PERMISSIVE_6 = 9 1505 | FOV_PERMISSIVE_7 = 10 1506 | FOV_PERMISSIVE_8 = 11 1507 | FOV_RESTRICTIVE = 12 1508 | NB_FOV_ALGORITHMS = 13 1509 | 1510 | def FOV_PERMISSIVE(p) : 1511 | return FOV_PERMISSIVE_0+p 1512 | 1513 | def map_new(w, h): 1514 | return _lib.TCOD_map_new(w, h) 1515 | 1516 | def map_copy(source, dest): 1517 | return _lib.TCOD_map_copy(source, dest) 1518 | 1519 | def map_set_properties(m, x, y, isTrans, isWalk): 1520 | _lib.TCOD_map_set_properties(m, x, y, c_int(isTrans), c_int(isWalk)) 1521 | 1522 | def map_clear(m,walkable=False,transparent=False): 1523 | _lib.TCOD_map_clear(m,c_int(walkable),c_int(transparent)) 1524 | 1525 | def map_compute_fov(m, x, y, radius=0, light_walls=True, algo=FOV_RESTRICTIVE ): 1526 | _lib.TCOD_map_compute_fov(m, x, y, c_int(radius), c_bool(light_walls), c_int(algo)) 1527 | 1528 | def map_is_in_fov(m, x, y): 1529 | return _lib.TCOD_map_is_in_fov(m, x, y) 1530 | 1531 | def map_is_transparent(m, x, y): 1532 | return _lib.TCOD_map_is_transparent(m, x, y) 1533 | 1534 | def map_is_walkable(m, x, y): 1535 | return _lib.TCOD_map_is_walkable(m, x, y) 1536 | 1537 | def map_delete(m): 1538 | return _lib.TCOD_map_delete(m) 1539 | 1540 | def map_get_width(map): 1541 | return _lib.TCOD_map_get_width(map) 1542 | 1543 | def map_get_height(map): 1544 | return _lib.TCOD_map_get_height(map) 1545 | 1546 | ############################ 1547 | # pathfinding module 1548 | ############################ 1549 | _lib.TCOD_path_compute.restype = c_bool 1550 | _lib.TCOD_path_is_empty.restype = c_bool 1551 | _lib.TCOD_path_walk.restype = c_bool 1552 | 1553 | PATH_CBK_FUNC = CFUNCTYPE(c_float, c_int, c_int, c_int, c_int, py_object) 1554 | 1555 | def path_new_using_map(m, dcost=1.41): 1556 | return (_lib.TCOD_path_new_using_map(c_void_p(m), c_float(dcost)), None) 1557 | 1558 | def path_new_using_function(w, h, func, userdata=0, dcost=1.41): 1559 | cbk_func = PATH_CBK_FUNC(func) 1560 | return (_lib.TCOD_path_new_using_function(w, h, cbk_func, 1561 | py_object(userdata), c_float(dcost)), cbk_func) 1562 | 1563 | def path_compute(p, ox, oy, dx, dy): 1564 | return _lib.TCOD_path_compute(p[0], ox, oy, dx, dy) 1565 | 1566 | def path_get_origin(p): 1567 | x = c_int() 1568 | y = c_int() 1569 | _lib.TCOD_path_get_origin(p[0], byref(x), byref(y)) 1570 | return x.value, y.value 1571 | 1572 | def path_get_destination(p): 1573 | x = c_int() 1574 | y = c_int() 1575 | _lib.TCOD_path_get_destination(p[0], byref(x), byref(y)) 1576 | return x.value, y.value 1577 | 1578 | def path_size(p): 1579 | return _lib.TCOD_path_size(p[0]) 1580 | 1581 | def path_reverse(p): 1582 | _lib.TCOD_path_reverse(p[0]) 1583 | 1584 | def path_get(p, idx): 1585 | x = c_int() 1586 | y = c_int() 1587 | _lib.TCOD_path_get(p[0], idx, byref(x), byref(y)) 1588 | return x.value, y.value 1589 | 1590 | def path_is_empty(p): 1591 | return _lib.TCOD_path_is_empty(p[0]) 1592 | 1593 | def path_walk(p, recompute): 1594 | x = c_int() 1595 | y = c_int() 1596 | if _lib.TCOD_path_walk(p[0], byref(x), byref(y), c_int(recompute)): 1597 | return x.value, y.value 1598 | return None,None 1599 | 1600 | def path_delete(p): 1601 | _lib.TCOD_path_delete(p[0]) 1602 | 1603 | _lib.TCOD_dijkstra_path_set.restype = c_bool 1604 | _lib.TCOD_dijkstra_is_empty.restype = c_bool 1605 | _lib.TCOD_dijkstra_path_walk.restype = c_bool 1606 | _lib.TCOD_dijkstra_get_distance.restype = c_float 1607 | 1608 | def dijkstra_new(m, dcost=1.41): 1609 | return (_lib.TCOD_dijkstra_new(c_void_p(m), c_float(dcost)), None) 1610 | 1611 | def dijkstra_new_using_function(w, h, func, userdata=0, dcost=1.41): 1612 | cbk_func = PATH_CBK_FUNC(func) 1613 | return (_lib.TCOD_path_dijkstra_using_function(w, h, cbk_func, 1614 | py_object(userdata), c_float(dcost)), cbk_func) 1615 | 1616 | def dijkstra_compute(p, ox, oy): 1617 | _lib.TCOD_dijkstra_compute(p[0], c_int(ox), c_int(oy)) 1618 | 1619 | def dijkstra_path_set(p, x, y): 1620 | return _lib.TCOD_dijkstra_path_set(p[0], c_int(x), c_int(y)) 1621 | 1622 | def dijkstra_get_distance(p, x, y): 1623 | return _lib.TCOD_dijkstra_get_distance(p[0], c_int(x), c_int(y)) 1624 | 1625 | def dijkstra_size(p): 1626 | return _lib.TCOD_dijkstra_size(p[0]) 1627 | 1628 | def dijkstra_reverse(p): 1629 | _lib.TCOD_dijkstra_reverse(p[0]) 1630 | 1631 | def dijkstra_get(p, idx): 1632 | x = c_int() 1633 | y = c_int() 1634 | _lib.TCOD_dijkstra_get(p[0], c_int(idx), byref(x), byref(y)) 1635 | return x.value, y.value 1636 | 1637 | def dijkstra_is_empty(p): 1638 | return _lib.TCOD_dijkstra_is_empty(p[0]) 1639 | 1640 | def dijkstra_path_walk(p): 1641 | x = c_int() 1642 | y = c_int() 1643 | if _lib.TCOD_dijkstra_path_walk(p[0], byref(x), byref(y)): 1644 | return x.value, y.value 1645 | return None,None 1646 | 1647 | def dijkstra_delete(p): 1648 | _lib.TCOD_dijkstra_delete(p[0]) 1649 | 1650 | ############################ 1651 | # bsp module 1652 | ############################ 1653 | class _CBsp(Structure): 1654 | _fields_ = [('next', c_void_p), 1655 | ('father', c_void_p), 1656 | ('son', c_void_p), 1657 | ('x', c_int), 1658 | ('y', c_int), 1659 | ('w', c_int), 1660 | ('h', c_int), 1661 | ('position', c_int), 1662 | ('level', c_uint8), 1663 | ('horizontal', c_bool), 1664 | ] 1665 | 1666 | _lib.TCOD_bsp_new_with_size.restype = POINTER(_CBsp) 1667 | _lib.TCOD_bsp_left.restype = POINTER(_CBsp) 1668 | _lib.TCOD_bsp_right.restype = POINTER(_CBsp) 1669 | _lib.TCOD_bsp_father.restype = POINTER(_CBsp) 1670 | _lib.TCOD_bsp_is_leaf.restype = c_bool 1671 | _lib.TCOD_bsp_contains.restype = c_bool 1672 | _lib.TCOD_bsp_find_node.restype = POINTER(_CBsp) 1673 | 1674 | BSP_CBK_FUNC = CFUNCTYPE(c_int, c_void_p, c_void_p) 1675 | 1676 | # python class encapsulating the _CBsp pointer 1677 | class Bsp(object): 1678 | def __init__(self, cnode): 1679 | pcbsp = cast(cnode, POINTER(_CBsp)) 1680 | self.p = pcbsp 1681 | 1682 | def getx(self): 1683 | return self.p.contents.x 1684 | def setx(self, value): 1685 | self.p.contents.x = value 1686 | x = property(getx, setx) 1687 | 1688 | def gety(self): 1689 | return self.p.contents.y 1690 | def sety(self, value): 1691 | self.p.contents.y = value 1692 | y = property(gety, sety) 1693 | 1694 | def getw(self): 1695 | return self.p.contents.w 1696 | def setw(self, value): 1697 | self.p.contents.w = value 1698 | w = property(getw, setw) 1699 | 1700 | def geth(self): 1701 | return self.p.contents.h 1702 | def seth(self, value): 1703 | self.p.contents.h = value 1704 | h = property(geth, seth) 1705 | 1706 | def getpos(self): 1707 | return self.p.contents.position 1708 | def setpos(self, value): 1709 | self.p.contents.position = value 1710 | position = property(getpos, setpos) 1711 | 1712 | def gethor(self): 1713 | return self.p.contents.horizontal 1714 | def sethor(self,value): 1715 | self.p.contents.horizontal = value 1716 | horizontal = property(gethor, sethor) 1717 | 1718 | def getlev(self): 1719 | return self.p.contents.level 1720 | def setlev(self,value): 1721 | self.p.contents.level = value 1722 | level = property(getlev, setlev) 1723 | 1724 | 1725 | def bsp_new_with_size(x, y, w, h): 1726 | return Bsp(_lib.TCOD_bsp_new_with_size(x, y, w, h)) 1727 | 1728 | def bsp_split_once(node, horizontal, position): 1729 | _lib.TCOD_bsp_split_once(node.p, c_int(horizontal), position) 1730 | 1731 | def bsp_split_recursive(node, randomizer, nb, minHSize, minVSize, maxHRatio, 1732 | maxVRatio): 1733 | _lib.TCOD_bsp_split_recursive(node.p, randomizer, nb, minHSize, minVSize, 1734 | c_float(maxHRatio), c_float(maxVRatio)) 1735 | 1736 | def bsp_resize(node, x, y, w, h): 1737 | _lib.TCOD_bsp_resize(node.p, x, y, w, h) 1738 | 1739 | def bsp_left(node): 1740 | return Bsp(_lib.TCOD_bsp_left(node.p)) 1741 | 1742 | def bsp_right(node): 1743 | return Bsp(_lib.TCOD_bsp_right(node.p)) 1744 | 1745 | def bsp_father(node): 1746 | return Bsp(_lib.TCOD_bsp_father(node.p)) 1747 | 1748 | def bsp_is_leaf(node): 1749 | return _lib.TCOD_bsp_is_leaf(node.p) 1750 | 1751 | def bsp_contains(node, cx, cy): 1752 | return _lib.TCOD_bsp_contains(node.p, cx, cy) 1753 | 1754 | def bsp_find_node(node, cx, cy): 1755 | return Bsp(_lib.TCOD_bsp_find_node(node.p, cx, cy)) 1756 | 1757 | def _bsp_traverse(node, callback, userData, func): 1758 | # convert the c node into a python node 1759 | #before passing it to the actual callback 1760 | def node_converter(cnode, data): 1761 | node = Bsp(cnode) 1762 | return callback(node, data) 1763 | cbk_func = BSP_CBK_FUNC(node_converter) 1764 | func(node.p, cbk_func, userData) 1765 | 1766 | def bsp_traverse_pre_order(node, callback, userData=0): 1767 | _bsp_traverse(node, callback, userData, _lib.TCOD_bsp_traverse_pre_order) 1768 | 1769 | def bsp_traverse_in_order(node, callback, userData=0): 1770 | _bsp_traverse(node, callback, userData, _lib.TCOD_bsp_traverse_in_order) 1771 | 1772 | def bsp_traverse_post_order(node, callback, userData=0): 1773 | _bsp_traverse(node, callback, userData, _lib.TCOD_bsp_traverse_post_order) 1774 | 1775 | def bsp_traverse_level_order(node, callback, userData=0): 1776 | _bsp_traverse(node, callback, userData, _lib.TCOD_bsp_traverse_level_order) 1777 | 1778 | def bsp_traverse_inverted_level_order(node, callback, userData=0): 1779 | _bsp_traverse(node, callback, userData, 1780 | _lib.TCOD_bsp_traverse_inverted_level_order) 1781 | 1782 | def bsp_remove_sons(node): 1783 | _lib.TCOD_bsp_remove_sons(node.p) 1784 | 1785 | def bsp_delete(node): 1786 | _lib.TCOD_bsp_delete(node.p) 1787 | 1788 | ############################ 1789 | # heightmap module 1790 | ############################ 1791 | class _CHeightMap(Structure): 1792 | _fields_=[('w', c_int), 1793 | ('h', c_int), 1794 | ('values', POINTER(c_float)), 1795 | ] 1796 | 1797 | _lib.TCOD_heightmap_new.restype = POINTER(_CHeightMap) 1798 | _lib.TCOD_heightmap_get_value.restype = c_float 1799 | _lib.TCOD_heightmap_has_land_on_border.restype = c_bool 1800 | 1801 | class HeightMap(object): 1802 | def __init__(self, chm): 1803 | pchm = cast(chm, POINTER(_CHeightMap)) 1804 | self.p = pchm 1805 | 1806 | def getw(self): 1807 | return self.p.contents.w 1808 | def setw(self, value): 1809 | self.p.contents.w = value 1810 | w = property(getw, setw) 1811 | 1812 | def geth(self): 1813 | return self.p.contents.h 1814 | def seth(self, value): 1815 | self.p.contents.h = value 1816 | h = property(geth, seth) 1817 | 1818 | def heightmap_new(w, h): 1819 | phm = _lib.TCOD_heightmap_new(w, h) 1820 | return HeightMap(phm) 1821 | 1822 | def heightmap_set_value(hm, x, y, value): 1823 | _lib.TCOD_heightmap_set_value(hm.p, x, y, c_float(value)) 1824 | 1825 | def heightmap_add(hm, value): 1826 | _lib.TCOD_heightmap_add(hm.p, c_float(value)) 1827 | 1828 | def heightmap_scale(hm, value): 1829 | _lib.TCOD_heightmap_scale(hm.p, c_float(value)) 1830 | 1831 | def heightmap_clear(hm): 1832 | _lib.TCOD_heightmap_clear(hm.p) 1833 | 1834 | def heightmap_clamp(hm, mi, ma): 1835 | _lib.TCOD_heightmap_clamp(hm.p, c_float(mi),c_float(ma)) 1836 | 1837 | def heightmap_copy(hm1, hm2): 1838 | _lib.TCOD_heightmap_copy(hm1.p, hm2.p) 1839 | 1840 | def heightmap_normalize(hm, mi=0.0, ma=1.0): 1841 | _lib.TCOD_heightmap_normalize(hm.p, c_float(mi), c_float(ma)) 1842 | 1843 | def heightmap_lerp_hm(hm1, hm2, hm3, coef): 1844 | _lib.TCOD_heightmap_lerp_hm(hm1.p, hm2.p, hm3.p, c_float(coef)) 1845 | 1846 | def heightmap_add_hm(hm1, hm2, hm3): 1847 | _lib.TCOD_heightmap_add_hm(hm1.p, hm2.p, hm3.p) 1848 | 1849 | def heightmap_multiply_hm(hm1, hm2, hm3): 1850 | _lib.TCOD_heightmap_multiply_hm(hm1.p, hm2.p, hm3.p) 1851 | 1852 | def heightmap_add_hill(hm, x, y, radius, height): 1853 | _lib.TCOD_heightmap_add_hill(hm.p, c_float( x), c_float( y), 1854 | c_float( radius), c_float( height)) 1855 | 1856 | def heightmap_dig_hill(hm, x, y, radius, height): 1857 | _lib.TCOD_heightmap_dig_hill(hm.p, c_float( x), c_float( y), 1858 | c_float( radius), c_float( height)) 1859 | 1860 | def heightmap_rain_erosion(hm, nbDrops, erosionCoef, sedimentationCoef, rnd=0): 1861 | _lib.TCOD_heightmap_rain_erosion(hm.p, nbDrops, c_float( erosionCoef), 1862 | c_float( sedimentationCoef), rnd) 1863 | 1864 | def heightmap_kernel_transform(hm, kernelsize, dx, dy, weight, minLevel, 1865 | maxLevel): 1866 | FARRAY = c_float * kernelsize 1867 | IARRAY = c_int * kernelsize 1868 | cdx = IARRAY(*dx) 1869 | cdy = IARRAY(*dy) 1870 | cweight = FARRAY(*weight) 1871 | _lib.TCOD_heightmap_kernel_transform(hm.p, kernelsize, cdx, cdy, cweight, 1872 | c_float(minLevel), c_float(maxLevel)) 1873 | 1874 | def heightmap_add_voronoi(hm, nbPoints, nbCoef, coef, rnd=0): 1875 | FARRAY = c_float * nbCoef 1876 | ccoef = FARRAY(*coef) 1877 | _lib.TCOD_heightmap_add_voronoi(hm.p, nbPoints, nbCoef, ccoef, rnd) 1878 | 1879 | def heightmap_add_fbm(hm, noise, mulx, muly, addx, addy, octaves, delta, scale): 1880 | _lib.TCOD_heightmap_add_fbm(hm.p, noise, c_float(mulx), c_float(muly), 1881 | c_float(addx), c_float(addy), 1882 | c_float(octaves), c_float(delta), 1883 | c_float(scale)) 1884 | def heightmap_scale_fbm(hm, noise, mulx, muly, addx, addy, octaves, delta, 1885 | scale): 1886 | _lib.TCOD_heightmap_scale_fbm(hm.p, noise, c_float(mulx), c_float(muly), 1887 | c_float(addx), c_float(addy), 1888 | c_float(octaves), c_float(delta), 1889 | c_float(scale)) 1890 | def heightmap_dig_bezier(hm, px, py, startRadius, startDepth, endRadius, 1891 | endDepth): 1892 | IARRAY = c_int * 4 1893 | cpx = IARRAY(*px) 1894 | cpy = IARRAY(*py) 1895 | _lib.TCOD_heightmap_dig_bezier(hm.p, cpx, cpy, c_float(startRadius), 1896 | c_float(startDepth), c_float(endRadius), 1897 | c_float(endDepth)) 1898 | 1899 | def heightmap_get_value(hm, x, y): 1900 | return _lib.TCOD_heightmap_get_value(hm.p, x, y) 1901 | 1902 | def heightmap_get_interpolated_value(hm, x, y): 1903 | return _lib.TCOD_heightmap_get_interpolated_value(hm.p, c_float(x), 1904 | c_float(y)) 1905 | 1906 | def heightmap_get_slope(hm, x, y): 1907 | return _lib.TCOD_heightmap_get_slope(hm.p, x, y) 1908 | 1909 | def heightmap_get_normal(hm, x, y, waterLevel): 1910 | FARRAY = c_float * 3 1911 | cn = FARRAY() 1912 | _lib.TCOD_heightmap_get_normal(hm.p, c_float(x), c_float(y), cn, 1913 | c_float(waterLevel)) 1914 | return cn[0], cn[1], cn[2] 1915 | 1916 | def heightmap_count_cells(hm, mi, ma): 1917 | return _lib.TCOD_heightmap_count_cells(hm.p, c_float(mi), c_float(ma)) 1918 | 1919 | def heightmap_has_land_on_border(hm, waterlevel): 1920 | return _lib.TCOD_heightmap_has_land_on_border(hm.p, c_float(waterlevel)) 1921 | 1922 | def heightmap_get_minmax(hm): 1923 | mi = c_float() 1924 | ma = c_float() 1925 | _lib.TCOD_heightmap_get_minmax(hm.p, byref(mi), byref(ma)) 1926 | return mi.value, ma.value 1927 | 1928 | def heightmap_delete(hm): 1929 | _lib.TCOD_heightmap_delete(hm.p) 1930 | 1931 | 1932 | ############################ 1933 | # name generator module 1934 | ############################ 1935 | _lib.TCOD_namegen_generate.restype = c_char_p 1936 | _lib.TCOD_namegen_generate_custom.restype = c_char_p 1937 | 1938 | def namegen_parse(filename,random=0) : 1939 | _lib.TCOD_namegen_parse(filename,random) 1940 | 1941 | def namegen_generate(name) : 1942 | return _lib.TCOD_namegen_generate(name, 0) 1943 | 1944 | def namegen_generate_custom(name, rule) : 1945 | return _lib.TCOD_namegen_generate(name, rule, 0) 1946 | 1947 | def namegen_get_sets(): 1948 | nb=_lib.TCOD_namegen_get_nb_sets_wrapper() 1949 | SARRAY = c_char_p * nb; 1950 | setsa = SARRAY() 1951 | _lib.TCOD_namegen_get_sets_wrapper(setsa) 1952 | return list(setsa) 1953 | 1954 | def namegen_destroy() : 1955 | _lib.TCOD_namegen_destroy() 1956 | 1957 | 1958 | --------------------------------------------------------------------------------