Package tdl
[frames] | no frames]

Source Code for Package tdl

   1  """ 
   2      The documentation for python-tdl.  A Pythonic port of 
   3      U{libtcod<http://doryen.eptalys.net/libtcod/>}. 
   4       
   5      You can find the project page on Google Code 
   6      U{here<http://code.google.com/p/python-tdl/>}. 
   7   
   8      Getting Started 
   9      =============== 
  10        Once the library is imported you can load the font you want to use with 
  11        L{tdl.setFont}. 
  12        This is optional and when skipped will use a decent default font. 
  13         
  14        After that you call L{tdl.init} to set the size of the window and get the 
  15        root console in return. 
  16        This console is the canvas to what will appear on the screen. 
  17   
  18      Indexing Consoles 
  19      ================= 
  20        For most methods taking a position you can use Python-style negative 
  21        indexes to refer to the opposite side of a console with (-1, -1) 
  22        starting at the bottom right. 
  23        You can also check if a point is part of a console using containment 
  24        logic i.e. ((x, y) in console). 
  25         
  26      Drawing 
  27      ======= 
  28        Once you have the root console from L{tdl.init} you can start drawing on 
  29        it using a method such as L{Console.drawChar}. 
  30        When using this method you can have the char parameter be an integer or a 
  31        single character string. 
  32        The fgcolor and bgcolor parameters expect a three item list 
  33        [red, green, blue] with integers in the 0-255 range with [0, 0, 0] being 
  34        black and [255, 255, 255] being white. 
  35        Or instead you can use None for any of the three parameters to tell the 
  36        library to keep what is at that spot instead of overwriting it. 
  37        After the drawing functions are called a call to L{tdl.flush} will update 
  38        the screen. 
  39  """ 
  40   
  41  import sys 
  42  import os 
  43   
  44  import ctypes 
  45  import weakref 
  46  import array 
  47  import itertools 
  48  import textwrap 
  49  import struct 
  50  import re 
  51  import warnings 
  52   
  53  from . import event, map, noise 
  54  from .__tcod import _lib, _Color, _unpackfile 
  55   
  56  _IS_PYTHON3 = (sys.version_info[0] == 3) 
  57   
  58  if _IS_PYTHON3: # some type lists to use with isinstance 
  59      _INTTYPES = (int,) 
  60      _NUMTYPES = (int, float) 
  61      _STRTYPES = (str, bytes) 
  62  else: 
  63      _INTTYPES = (int, long) 
  64      _NUMTYPES = (int, long, float) 
  65      _STRTYPES = (str,) 
66 67 -def _encodeString(string): # still used for filepaths, and that's about it
68 "changes string into bytes if running in python 3, for sending to ctypes" 69 if _IS_PYTHON3 and isinstance(string, str): 70 return string.encode() 71 return string 72
73 #def _formatString(string): 74 # pass 75 76 -def _formatChar(char):
77 """Prepares a single character for passing to ctypes calls, needs to return 78 an integer but can also pass None which will keep the current character 79 instead of overwriting it. 80 81 This is called often and needs to be optimized whenever possible. 82 """ 83 if char is None: 84 return None 85 if isinstance(char, _INTTYPES): 86 return char 87 if isinstance(char, _STRTYPES) and len(char) == 1: 88 return ord(char) 89 raise TypeError('Expected char parameter to be a single character string, number, or None, got: %s' % repr(char))
90 91 _fontinitialized = False 92 _rootinitialized = False 93 _rootConsoleRef = None 94 # remove dots from common functions 95 _setchar = _lib.TCOD_console_set_char 96 _setfore = _lib.TCOD_console_set_char_foreground 97 _setback = _lib.TCOD_console_set_char_background 98 _setcharEX = _lib.TCOD_console_put_char_ex
99 -def _verify_colors(*colors):
100 """Used internally. 101 Raise an assertion error if the parameters can not be converted into colors. 102 """ 103 for color in colors: 104 assert _iscolor(color), 'a color must be a 3 item tuple, web format, or None, received %s' % repr(color) 105 return True
106
107 -def _iscolor(color):
108 """Used internally. 109 A debug function to see if an object can be used as a TCOD color struct. 110 None counts as a parameter to keep the current colors instead. 111 112 This function is often part of an inner-loop and can slow a program down. 113 It has been made to work with assert and can be skipped with the -O flag. 114 Still it's called often and must be optimized. 115 """ 116 if color is None: 117 return True 118 if isinstance(color, (tuple, list, _Color)): 119 return len(color) == 3 120 if isinstance(color, _INTTYPES): 121 return True 122 return False
123
124 ## not using this for now 125 #class Color(object): 126 # 127 # def __init__(self, r, g, b): 128 # self._color = (r, g, b) 129 # self._ctype = None 130 # 131 # def _getCType(self): 132 # if not self._ctype: 133 # self._ctype = _Color(*self._color) 134 # return self._ctype 135 # 136 # def __len__(self): 137 # return 3 138 139 -def _formatColor(color):
140 """Format the color to ctypes 141 """ 142 if color is None: 143 return None 144 if isinstance(color, _Color): 145 return color 146 #if isinstance(color, Color): 147 # return color._getCType() 148 if isinstance(color, _INTTYPES): 149 # format a web style color with the format 0xRRGGBB 150 return _Color(color >> 16 & 0xff, color >> 8 & 0xff, color & 0xff) 151 return _Color(*color)
152
153 -def _getImageSize(filename):
154 """Try to get the width and height of a bmp of png image file""" 155 file = open(filename, 'rb') 156 if file.read(8) == b'\x89PNG\r\n\x1a\n': # PNG 157 while 1: 158 length, = struct.unpack('>i', file.read(4)) 159 chunkID = file.read(4) 160 if chunkID == '': # EOF 161 return None 162 if chunkID == b'IHDR': 163 # return width, height 164 return struct.unpack('>ii', file.read(8)) 165 file.seek(4 + length, 1) 166 file.seek(0) 167 if file.read(8) == b'BM': # Bitmap 168 file.seek(18, 0) # skip to size data 169 # return width, height 170 return struct.unpack('<ii', file.read(8))
171 # return None on error, unknown file
172 173 -class TDLError(Exception):
174 """ 175 The catch all for most TDL specific errors. 176 """
177
178 -class _MetaConsole(object):
179 """ 180 Contains methods shared by both the L{Console} and L{Window} classes. 181 """ 182 __slots__ = ('width', 'height', 'console', '__weakref__', '__dict__') 183
184 - def drawChar(self, x, y, char, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
185 """Draws a single character. 186 187 @type x: int 188 @param x: X coordinate to draw at. 189 @type y: int 190 @param y: Y coordinate to draw at. 191 192 @type char: int, string, or None 193 @param char: Should be an integer, single character string, or None. 194 195 You can set the char parameter as None if you only want to change 196 the colors of the tile. 197 198 @type fgcolor: (r, g, b) or None 199 @param fgcolor: For fgcolor and bgcolor you use a 3 item list with 200 integers ranging 0-255 or None. 201 202 None will keep the current color at this position unchanged. 203 @type bgcolor: (r, g, b) or None 204 @param bgcolor: Background color. See fgcolor 205 206 @raise AssertionError: Having x or y values that can't be placed inside 207 of the console will raise an AssertionError. 208 You can use always use ((x, y) in console) to 209 check if a tile is drawable. 210 """ 211 212 assert _verify_colors(fgcolor, bgcolor) 213 x, y = self._normalizePoint(x, y) 214 x, y = ctypes.c_int(x), ctypes.c_int(y) 215 self._setChar(x, y, _formatChar(char), 216 _formatColor(fgcolor), _formatColor(bgcolor))
217
218 - def drawStr(self, x, y, string, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
219 """Draws a string starting at x and y. Optinally colored. 220 221 A string that goes past the right side will wrap around. A string 222 wraping to below the console will raise a L{TDLError} but will still be 223 written out. This means you can safely ignore the errors with a 224 try... except block if you're fine with partily written strings. 225 226 \\r and \\n are drawn on the console as normal character tiles. No 227 special encoding is done and any string will translate to the character 228 table as is. 229 230 For a string drawing operation that respects special characters see the 231 L{Typewriter} class. 232 233 @type x: int 234 @param x: X coordinate to draw at. 235 @type y: int 236 @param y: Y coordinate to draw at. 237 238 @type string: string or iterable 239 @param string: Can be a string or an iterable of numbers. 240 241 Special characters are ignored and rendered as any other 242 character. 243 244 @type fgcolor: (r, g, b) or None 245 @param fgcolor: For fgcolor and bgcolor you use a 3 item list with 246 integers ranging 0-255 or None. 247 248 None will keep the current color at this position unchanged. 249 @type bgcolor: (r, g, b) or None 250 @param bgcolor: Background color. See fgcolor 251 252 @raise AssertionError: Having x or y values that can't be placed inside 253 of the console will raise an AssertionError. 254 255 You can use always use ((x, y) in console) to 256 check if a tile is drawable. 257 """ 258 259 x, y = self._normalizePoint(x, y) 260 assert _verify_colors(fgcolor, bgcolor) 261 fgcolor, bgcolor = _formatColor(fgcolor), _formatColor(bgcolor) 262 width, height = self.getSize() 263 batch = [] # prepare a batch operation 264 def _drawStrGen(x=x, y=y, string=string, width=width, height=height): 265 """Generator for drawStr 266 267 Iterates over ((x, y), ch) data for _setCharBatch, raising an 268 error if the end of the console is reached. 269 """ 270 for char in string: 271 if y == height: 272 raise TDLError('End of console reached.') 273 #batch.append(((x, y), _formatChar(char))) # ((x, y), ch) 274 yield((x, y), _formatChar(char)) 275 x += 1 # advance cursor 276 if x == width: # line break 277 x = 0 278 y += 1
279 self._setCharBatch(_drawStrGen(), fgcolor, bgcolor)
280
281 - def drawRect(self, x, y, width, height, string, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
282 """Draws a rectangle starting from x and y and extending to width and height. 283 284 If width or height are None then it will extend to the edge of the console. 285 286 @type x: int 287 @param x: x coordinate to draw at. 288 @type y: int 289 @param y: y coordinate to draw at. 290 291 @type width: int or None 292 @param width: Width of the rectangle. 293 294 Can be None to extend to the bottom right of the 295 console or can be a negative number to be sized reltive 296 to the total size of the console. 297 @type height: int or None 298 @param height: Height of the rectangle. See width. 299 300 @type string: int, string, or None 301 @param string: Should be an integer, single character string, or None. 302 303 You can set the char parameter as None if you only want 304 to change the colors of an area. 305 306 @type fgcolor: (r, g, b) or None 307 @param fgcolor: For fgcolor and bgcolor you use a 3 item list with 308 integers ranging 0-255 or None. 309 310 None will keep the current color at this position unchanged. 311 @type bgcolor: (r, g, b) or None 312 @param bgcolor: Background color. See fgcolor 313 314 @raise AssertionError: Having x or y values that can't be placed inside 315 of the console will raise an AssertionError. 316 317 You can use always use ((x, y) in console) to 318 check if a tile is drawable. 319 """ 320 x, y, width, height = self._normalizeRect(x, y, width, height) 321 assert _verify_colors(fgcolor, bgcolor) 322 fgcolor, bgcolor = _formatColor(fgcolor), _formatColor(bgcolor) 323 char = _formatChar(string) 324 # use itertools to make an x,y grid 325 # using ctypes here reduces type converstions later 326 grid = itertools.product((ctypes.c_int(x) for x in range(x, x + width)), 327 (ctypes.c_int(y) for y in range(y, y + height))) 328 # zip the single character in a batch variable 329 batch = zip(grid, itertools.repeat(char, width * height)) 330 self._setCharBatch(batch, fgcolor, bgcolor, nullChar=(char is None))
331
332 - def drawFrame(self, x, y, width, height, string, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
333 """Similar to L{drawRect} but only draws the outline of the rectangle. 334 335 @type x: int 336 @param x: x coordinate to draw at. 337 @type y: int 338 @param y: y coordinate to draw at. 339 340 @type width: int or None 341 @param width: Width of the rectangle. 342 343 Can be None to extend to the bottom right of the 344 console or can be a negative number to be sized reltive 345 to the total size of the console. 346 @type height: int or None 347 @param height: Height of the rectangle. See width. 348 349 @type string: int, string, or None 350 @param string: Should be an integer, single character string, or None. 351 352 You can set the char parameter as None if you only want 353 to change the colors of an area. 354 355 @type fgcolor: (r, g, b) or None 356 @param fgcolor: For fgcolor and bgcolor you use a 3 item list with 357 integers ranging 0-255 or None. 358 359 None will keep the current color at this position unchanged. 360 @type bgcolor: (r, g, b) or None 361 @param bgcolor: Background color. See fgcolor 362 363 @raise AssertionError: Having x or y values that can't be placed inside 364 of the console will raise an AssertionError. 365 366 You can use always use ((x, y) in console) to 367 check if a tile is drawable. 368 """ 369 x, y, width, height = self._normalizeRect(x, y, width, height) 370 assert _verify_colors(fgcolor, bgcolor) 371 fgcolor, bgcolor = _formatColor(fgcolor), _formatColor(bgcolor) 372 char = _formatChar(string) 373 if width == 1 or height == 1: # it's just a single width line here 374 return self.drawRect(x, y, width, height, char, fgcolor, bgcolor) 375 376 # draw sides of frame with drawRect 377 self.drawRect(x, y, 1, height, char, fgcolor, bgcolor) 378 self.drawRect(x, y, width, 1, char, fgcolor, bgcolor) 379 self.drawRect(x + width - 1, y, 1, height, char, fgcolor, bgcolor) 380 self.drawRect(x, y + height - 1, width, 1, char, fgcolor, bgcolor)
381
382 - def _normalizePoint(self, x, y):
383 """Check if a point is in bounds and make minor adjustments. 384 385 Respects Pythons negative indexes. -1 starts at the bottom right. 386 Replaces the _drawable function 387 """ 388 assert isinstance(x, _INTTYPES), 'x must be an integer, got %s' % repr(x) 389 assert isinstance(y, _INTTYPES), 'y must be an integer, got %s' % repr(y) 390 391 assert (-self.width <= x < self.width) and (-self.height <= y < self.height), \ 392 ('(%i, %i) is an invalid postition on %s' % (x, y, self)) 393 394 # handle negative indexes 395 if x < 0: 396 x += self.width 397 if y < 0: 398 y += self.height 399 return (x, y)
400
401 - def _normalizeRect(self, x, y, width, height):
402 """Check if the rectangle is in bounds and make minor adjustments. 403 raise AssertionError's for any problems 404 """ 405 x, y = self._normalizePoint(x, y) # inherit _normalizePoint logic 406 407 assert width is None or isinstance(width, _INTTYPES), 'width must be an integer or None, got %s' % repr(width) 408 assert height is None or isinstance(height, _INTTYPES), 'height must be an integer or None, got %s' % repr(height) 409 410 # if width or height are None then extend them to the edge 411 if width is None: 412 width = self.width - x 413 elif width < 0: # handle negative numbers 414 width += self.width 415 width = max(0, width) # a 'too big' negative is clamped zero 416 if height is None: 417 height = self.height - y 418 height = max(0, height) 419 elif height < 0: 420 height += self.height 421 422 # reduce rect size to bounds 423 width = min(width, self.width - x) 424 height = min(height, self.height - y) 425 426 return x, y, width, height
427
428 - def blit(self, source, x=0, y=0, width=None, height=None, srcX=0, srcY=0):
429 """Blit another console or Window onto the current console. 430 431 By default it blits the entire source to the topleft corner. 432 433 @type source: L{Console} or L{Window} 434 @param source: Source window can be a L{Console} or L{Window} instance. 435 It can even blit to itself without any problems. 436 437 @type x: int 438 @param x: X coordinate to blit to. 439 @type y: int 440 @param y: Y coordinate to blit to. 441 442 @type width: int or None 443 @param width: Width of the rectangle. 444 445 Can be None to extend as far as possible to the 446 bottom right corner of the blit area or can be a negative 447 number to be sized reltive to the total size of the 448 B{destination} console. 449 @type height: int or None 450 @param height: Height of the rectangle. See width. 451 452 @type srcX: int 453 @param srcX: The source consoles x coordinate to blit from. 454 @type srcY: int 455 @param srcY: The source consoles y coordinate to blit from. 456 """ 457 # hardcode alpha settings for now 458 fgalpha=1.0 459 bgalpha=1.0 460 461 assert isinstance(source, (Console, Window)), "source muse be a Window or Console instance" 462 463 # handle negative indexes and rects 464 # negative width and height will be set realtive to the destination 465 # and will also be clamped to the smallest Console 466 x, y, width, height = self._normalizeRect(x, y, width, height) 467 srcX, srcY, width, height = source._normalizeRect(srcX, srcY, width, height) 468 469 # translate source and self if any of them are Window instances 470 srcX, srcY = source._translate(srcX, srcY) 471 source = source.console 472 x, y = self._translate(x, y) 473 self = self.console 474 475 if self == source: 476 # if we are the same console then we need a third console to hold 477 # onto the data, otherwise it tries to copy into itself and 478 # starts destroying everything 479 tmp = Console(width, height) 480 _lib.TCOD_console_blit(source, srcX, srcY, width, height, tmp, 0, 0, fgalpha, bgalpha) 481 _lib.TCOD_console_blit(tmp, 0, 0, width, height, self, x, y, fgalpha, bgalpha) 482 else: 483 _lib.TCOD_console_blit(source, srcX, srcY, width, height, self, x, y, fgalpha, bgalpha)
484
485 - def getSize(self):
486 """Return the size of the console as (width, height) 487 488 @rtype: (int, int) 489 """ 490 return self.width, self.height
491
492 - def scroll(self, x, y):
493 """Scroll the contents of the console in the direction of x,y. 494 495 Uncovered areas will be cleared. 496 @type x: int 497 @param x: Distance to scroll along x-axis 498 @type y: int 499 @param y: Distance to scroll along y-axis 500 """ 501 assert isinstance(x, _INTTYPES), "x must be an integer, got %s" % repr(x) 502 assert isinstance(y, _INTTYPES), "y must be an integer, got %s" % repr(x) 503 def getSlide(x, length): 504 """get the parameters needed to scroll the console in the given 505 direction with x 506 returns (x, length, srcx) 507 """ 508 if x > 0: 509 srcx = 0 510 length -= x 511 elif x < 0: 512 srcx = abs(x) 513 x = 0 514 length -= srcx 515 else: 516 srcx = 0 517 return x, length, srcx
518 def getCover(x, length): 519 """return the (x, width) ranges of what is covered and uncovered""" 520 cover = (0, length) # everything covered 521 uncover = None # nothing uncovered 522 if x > 0: # left side uncovered 523 cover = (x, length - x) 524 uncover = (0, x) 525 elif x < 0: # right side uncovered 526 x = abs(x) 527 cover = (0, length - x) 528 uncover = (length - x, x) 529 return cover, uncover 530 531 width, height = self.getSize() 532 if abs(x) >= width or abs(y) >= height: 533 return self.clear() # just clear the console normally 534 535 # get the ranges of the areas that will be uncovered 536 coverX, uncoverX = getCover(x, width) 537 coverY, uncoverY = getCover(y, height) 538 # so at this point we know that coverX and coverY makes a rect that 539 # encases the area that we end up blitting to. uncoverX/Y makes a 540 # rect in the corner of the uncovered area. So we need to combine 541 # the uncoverX/Y with coverY/X to make what's left of the uncovered 542 # area. Explaining it makes it mush easier to do now. 543 544 # But first we need to blit. 545 x, width, srcx = getSlide(x, width) 546 y, height, srcy = getSlide(y, height) 547 self.blit(self, x, y, width, height, srcx, srcy) 548 549 if uncoverX: # clear sides (0x20 is space) 550 self.drawRect(uncoverX[0], coverY[0], uncoverX[1], coverY[1], 0x20) 551 if uncoverY: # clear top/bottom 552 self.drawRect(coverX[0], uncoverY[0], coverX[1], uncoverY[1], 0x20) 553 if uncoverX and uncoverY: # clear corner 554 self.drawRect(uncoverX[0], uncoverY[0], uncoverX[1], uncoverY[1], 0x20) 555
556 - def getChar(self, x, y):
557 """Return the character and colors of a tile as (ch, fg, bg) 558 559 This method runs very slowly as is not recommended to be called 560 frequently. 561 562 @rtype: (int, (r, g, b), (r, g, b)) 563 @returns: Returns a 3-item tuple. The first item is an integer of the 564 character at the position (x, y) the second and third are the 565 foreground and background colors respectfully. 566 """ 567 raise NotImplementedError('Method here only exists for the docstring')
568
569 - def __contains__(self, position):
570 """Use ((x, y) in console) to check if a position is drawable on this console. 571 """ 572 x, y = position 573 return (0 <= x < self.width) and (0 <= y < self.height)
574
575 -class Console(_MetaConsole):
576 """Contains character and color data and can be drawn to. 577 578 The console created by the L{tdl.init} function is the root console and is the 579 console that is rendered to the screen with L{flush}. 580 581 Any console created from the Console class is an off-screen console that 582 can be drawn on before being L{blit} to the root console. 583 """ 584 585 __slots__ = ('_as_parameter_', '_typewriter') 586
587 - def __init__(self, width, height):
588 """Create a new offscreen console. 589 590 @type width: int 591 @param width: Width of the console in tiles 592 @type height: int 593 @param height: Height of the console in tiles 594 """ 595 if not _rootinitialized: 596 raise TDLError('Can not create Console\'s before tdl.init') 597 self._as_parameter_ = _lib.TCOD_console_new(width, height) 598 self.console = self 599 self.width = width 600 self.height = height 601 self._typewriter = None # "typewriter lock", makes sure the colors are set to the typewriter
602 603 @classmethod
604 - def _newConsole(cls, console):
605 """Make a Console instance, from a console ctype""" 606 self = cls.__new__(cls) 607 self._as_parameter_ = console 608 self.console = self 609 self.width = _lib.TCOD_console_get_width(self) 610 self.height = _lib.TCOD_console_get_height(self) 611 self._typewriter = None 612 return self
613
614 - def __del__(self):
615 """ 616 If the main console is garbage collected then the window will be closed as well 617 """ 618 # If this is the root console the window will close when collected 619 try: 620 if isinstance(self._as_parameter_, ctypes.c_void_p): 621 global _rootinitialized, _rootConsoleRef 622 _rootinitialized = False 623 _rootConsoleRef = None 624 _lib.TCOD_console_delete(self) 625 except StandardError: 626 pass # I forget why I put this here but I'm to afraid to delete it
627
628 - def _replace(self, console):
629 """Used internally 630 631 Mostly used just to replace this Console object with the root console 632 If another Console object is used then they are swapped 633 """ 634 if isinstance(console, Console): 635 self._as_parameter_, console._as_parameter_ = \ 636 console._as_parameter_, self._as_parameter_ # swap tcod consoles 637 else: 638 self._as_parameter_ = console 639 self.width = _lib.TCOD_console_get_width(self) 640 self.height = _lib.TCOD_console_get_height(self) 641 return self
642
643 - def _translate(self, x, y):
644 """Convertion x and y to their position on the root Console for this Window 645 646 Because this is a Console instead of a Window we return the paramaters 647 untouched""" 648 return x, y
649
650 - def clear(self, fgcolor=(0, 0, 0), bgcolor=(0, 0, 0)):
651 """Clears the entire Console. 652 653 @type fgcolor: (r, g, b) 654 @param fgcolor: Foreground color. 655 656 Must be a 3-item list with integers that range 0-255. 657 658 Unlike most other operations you can not use None here. 659 @type bgcolor: (r, g, b) 660 @param bgcolor: Background color. See fgcolor. 661 """ 662 assert _verify_colors(fgcolor, bgcolor) 663 assert fgcolor and bgcolor, 'Can not use None with clear' 664 self._typewriter = None 665 _lib.TCOD_console_set_default_background(self, _formatColor(bgcolor)) 666 _lib.TCOD_console_set_default_foreground(self, _formatColor(fgcolor)) 667 _lib.TCOD_console_clear(self)
668
669 - def _setChar(self, x, y, char, fgcolor=None, bgcolor=None, bgblend=1):
670 """ 671 Sets a character. 672 This is called often and is designed to be as fast as possible. 673 674 Because of the need for speed this function will do NO TYPE CHECKING 675 AT ALL, it's up to the drawing functions to use the functions: 676 _formatChar and _formatColor before passing to this.""" 677 # buffer values as ctypes objects 678 console = self._as_parameter_ 679 680 if char is not None and fgcolor is not None and bgcolor is not None: 681 _setcharEX(console, x, y, char, fgcolor, bgcolor) 682 return 683 if char is not None: 684 _setchar(console, x, y, char) 685 if fgcolor is not None: 686 _setfore(console, x, y, fgcolor) 687 if bgcolor is not None: 688 _setback(console, x, y, bgcolor, bgblend)
689
690 - def _setCharBatch(self, batch, fgcolor, bgcolor, bgblend=1, nullChar=False):
691 """ 692 Try to perform a batch operation otherwise fall back to _setChar. 693 If fgcolor and bgcolor are defined then this is faster but not by very 694 much. 695 696 batch is a iterable of [(x, y), ch] items 697 """ 698 if fgcolor and not nullChar: 699 # buffer values as ctypes objects 700 self._typewriter = None # clear the typewriter as colors will be set 701 console = self._as_parameter_ 702 if not bgcolor: 703 bgblend = 0 704 bgblend = ctypes.c_int(bgblend) 705 706 _lib.TCOD_console_set_default_background(console, bgcolor) 707 _lib.TCOD_console_set_default_foreground(console, fgcolor) 708 _putChar = _lib.TCOD_console_put_char # remove dots and make local 709 for (x, y), char in batch: 710 _putChar(console, x, y, char, bgblend) 711 else: 712 for (x, y), char in batch: 713 self._setChar(x, y, char, fgcolor, bgcolor, bgblend)
714
715 - def getChar(self, x, y):
716 # inherit docstring 717 x, y = self._normalizePoint(x, y) 718 char = _lib.TCOD_console_get_char(self, x, y) 719 bgcolor = _lib.TCOD_console_get_char_background_wrapper(self, x, y) 720 fgcolor = _lib.TCOD_console_get_char_foreground_wrapper(self, x, y) 721 return char, tuple(fgcolor), tuple(bgcolor)
722
723 - def __repr__(self):
724 return "<Console (Width=%i Height=%i)>" % (self.width, self.height)
725
726 727 -class Window(_MetaConsole):
728 """A Window contains a small isolated part of a Console. 729 730 Drawing on the Window draws on the Console. 731 732 Making a Window and setting its width or height to None will extend it to 733 the edge of the console. 734 """ 735 736 __slots__ = ('parent', 'x', 'y') 737
738 - def __init__(self, console, x, y, width, height):
739 """Isolate part of a L{Console} or L{Window} instance. 740 741 @type console: L{Console} or L{Window} 742 @param console: The parent object which can be a L{Console} or another 743 L{Window} instance. 744 745 @type x: int 746 @param x: X coordinate to place the Window. 747 748 This follows the normal rules for indexing so you can use a 749 negative integer to place the Window relative to the bottom 750 right of the parent Console instance. 751 @type y: int 752 @param y: Y coordinate to place the Window. 753 754 See x. 755 756 @type width: int or None 757 @param width: Width of the Window. 758 759 Can be None to extend as far as possible to the 760 bottom right corner of the parent Console or can be a 761 negative number to be sized reltive to the Consoles total 762 size. 763 @type height: int or None 764 @param height: Height of the Window. 765 766 See width. 767 """ 768 assert isinstance(console, (Console, Window)), 'console parameter must be a Console or Window instance, got %s' % repr(console) 769 self.parent = console 770 self.x, self.y, self.width, self.height = console._normalizeRect(x, y, width, height) 771 if isinstance(console, Console): 772 self.console = console 773 else: 774 self.console = self.parent.console
775
776 - def _translate(self, x, y):
777 """Convertion x and y to their position on the root Console""" 778 # we add our position relative to our parent and then call then next parent up 779 return self.parent._translate((x + self.x), (y + self.y))
780
781 - def clear(self, fgcolor=(0, 0, 0), bgcolor=(0, 0, 0)):
782 """Clears the entire Window. 783 784 @type fgcolor: (r, g, b) 785 @param fgcolor: Foreground color. 786 787 Must be a 3-item list with integers that range 0-255. 788 789 Unlike most other operations you can not use None here. 790 @type bgcolor: (r, g, b) 791 @param bgcolor: Background color. See fgcolor. 792 """ 793 assert _verify_colors(fgcolor, bgcolor) 794 assert fgcolor and bgcolor, 'Can not use None with clear' 795 self.draw_rect(0, 0, None, None, 0x20, fgcolor, bgcolor)
796
797 - def _setChar(self, x, y, char=None, fgcolor=None, bgcolor=None, bgblend=1):
798 self.parent._setChar((x + self.x), (y + self.y), char, fgcolor, bgcolor, bgblend)
799
800 - def _setCharBatch(self, batch, fgcolor, bgcolor, bgblend=1):
801 myX = self.x # remove dots for speed up 802 myY = self.y 803 self.parent._setCharBatch((((x + myX, y + myY), ch) for ((x, y), ch) in batch), 804 fgcolor, bgcolor, bgblend)
805 806
807 - def drawChar(self, x, y, char, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
808 # inherit docstring 809 x, y = self._normalizePoint(x, y) 810 self.parent.drawChar(x + self.x, y + self.y, char, fgcolor, bgcolor)
811
812 - def drawRect(self, x, y, width, height, string, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
813 # inherit docstring 814 x, y, width, height = self._normalizeRect(x, y, width, height) 815 self.parent.drawRect(x + self.x, y + self.y, width, height, string, fgcolor, bgcolor)
816
817 - def drawFrame(self, x, y, width, height, string, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
818 # inherit docstring 819 x, y, width, height = self._normalizeRect(x, y, width, height) 820 self.parent.drawFrame(x + self.x, y + self.y, width, height, string, fgcolor, bgcolor)
821
822 - def getChar(self, x, y):
823 # inherit docstring 824 x, y = self._normalizePoint(x, y) 825 return self.console.getChar(self._translate(x, y))
826
827 - def __repr__(self):
828 return "<Window(X=%i Y=%i Width=%i Height=%i)>" % (self.x, self.y, 829 self.width, 830 self.height)
831
832 833 -class Typewriter(object):
834 """Converts a console into a scrolling text log that respects special 835 characters. 836 837 This class works best on a L{Window} or off-screen L{Console} instance. 838 In a L{Window} for example the scrolling text is limited to the L{Window}'s 839 isolated area. 840 """ 841
842 - def __init__(self, console):
843 """Add a virtual cursor to a L{Console} or L{Window} instance. 844 845 @type console: L{Console} or L{Window} 846 """ 847 assert isinstance(console, (Console, Window)), 'console parameter must be a Console or Window instance, got %s' % repr(console) 848 self.parent = console 849 if isinstance(self.parent, Console): 850 self.console = self.parent 851 else: 852 self.console = self.parent.console 853 self.cursor = (0, 0) # cursor position 854 self.scrollMode = 'scroll' #can be 'scroll', 'error' 855 self.fgcolor = _formatColor((255, 255, 255)) 856 self.bgcolor = _formatColor((0, 0, 0)) 857 self._bgblend = 1 # SET
858
859 - def _normalize(self, x, y):
860 """return the normalized the cursor position.""" 861 width, height = self.parent.getSize() 862 while x >= width: 863 x -= width 864 y += 1 865 while y >= height: 866 if self.scrollMode == 'scroll': 867 y -= 1 868 self.parent.scroll(0, -1) 869 elif self.scrollMode == 'error': 870 # reset the cursor on error 871 self.cursor = (0, 0) 872 raise TDLError('Typewriter cursor has reached the end of the console') 873 return (x, y)
874
875 - def getCursor(self):
876 """Return the virtual cursor position. 877 878 @rtype: (int, int) 879 @return: Returns (x, y) a 2-integer tuple containing where the next 880 L{addChar} or L{addStr} will start at. 881 882 This can be changed with the L{move} method.""" 883 x, y = self.cursor 884 width, height = self.parent.getSize() 885 while x >= width: 886 x -= width 887 y += 1 888 if y >= height and self.scrollMode == 'scroll': 889 y = height - 1 890 return x, y
891
892 - def move(self, x, y):
893 """Move the virtual cursor. 894 895 @type x: int 896 @param x: X position to place the cursor. 897 @type y: int 898 @param y: Y position to place the cursor. 899 """ 900 self.cursor = self.parent._normalizePoint(x, y)
901
902 - def setFG(self, color):
903 """Change the foreground color""" 904 assert _iscolor(color) 905 assert color is not None 906 self.fgcolor = _formatColor(color) 907 if self.console._typewriter is self: 908 _lib.TCOD_console_set_default_foreground(self.console, self.fgcolor)
909
910 - def setBG(self, color):
911 """Change the background color""" 912 assert _iscolor(color) 913 assert color is not None 914 self.bgcolor = _formatColor(color) 915 if self.console._typewriter is self: 916 _lib.TCOD_console_set_default_background(self.console, self.bgcolor)
917
918 - def _updateConsole(self):
919 """Make sure the colors on a console match the Typewriter instance""" 920 if self.console._typewriter is not self: 921 self.console._typewriter = self 922 923 _lib.TCOD_console_set_default_background(self.console, self.bgcolor) 924 _lib.TCOD_console_set_default_foreground(self.console, self.fgcolor)
925 926
927 - def addChar(self, char):
928 """Draw a single character at the cursor.""" 929 if char == '\n': # line break 930 x = 0 931 y += 1 932 return 933 if char == '\r': # return 934 x = 0 935 return 936 x, y = self._normalize(*self.cursor) 937 self.cursor = [x + 1, y] # advance cursor on next draw 938 self._updateConsole() 939 x, y = self.parent._translate(x, y) 940 _lib.TCOD_console_put_char(self.console._as_parameter_, x, y, _formatChar(char), self._bgblend)
941 942
943 - def addStr(self, string):
944 """Write a string at the cursor. Handles special characters such as newlines. 945 946 @type string: string 947 @param string: 948 """ 949 x, y = self.cursor 950 for char in string: 951 if char == '\n': # line break 952 x = 0 953 y += 1 954 continue 955 if char == '\r': # return 956 x = 0 957 continue 958 x, y = self._normalize(x, y) 959 self.parent.drawChar(x, y, char, self.fgcolor, self.bgcolor) 960 x += 1 961 self.cursor = (x, y)
962
963 - def write(self, string):
964 """This method mimics basic file-like behaviour. 965 966 Because of this method you can replace sys.stdout or sys.stderr with 967 a L{Typewriter} instance. 968 969 @type string: string 970 """ 971 # some 'basic' line buffer stuff. 972 # there must be an easier way to do this. The textwrap module didn't 973 # help much. 974 x, y = self._normalize(*self.cursor) 975 width, height = self.parent.getSize() 976 wrapper = textwrap.TextWrapper(initial_indent=(' '*x), width=width) 977 writeLines = [] 978 for line in string.split('\n'): 979 if line: 980 writeLines += wrapper.wrap(line) 981 wrapper.initial_indent = '' 982 else: 983 writeLines.append([]) 984 985 for line in writeLines: 986 x, y = self._normalize(x, y) 987 self.parent.drawStr(x, y, line[x:], self.fgcolor, self.bgcolor) 988 y += 1 989 x = 0 990 y -= 1 991 self.cursor = (x, y)
992
993 994 -def init(width, height, title=None, fullscreen=False, renderer='OPENGL'):
995 """Start the main console with the given width and height and return the 996 root console. 997 998 Call the consoles drawing functions. Then remember to use L{tdl.flush} to 999 make what's drawn visible on the console. 1000 1001 @type width: int 1002 @param width: width of the root console (in tiles) 1003 1004 @type height: int 1005 @param height: height of the root console (in tiles) 1006 1007 @type title: string 1008 @param title: Text to display as the window title. 1009 1010 If left None it defaults to the running scripts filename. 1011 1012 @type fullscreen: boolean 1013 @param fullscreen: Can be set to True to start in fullscreen mode. 1014 1015 @type renderer: string 1016 @param renderer: Can be one of 'GLSL', 'OPENGL', or 'SDL'. 1017 1018 Due to way Python works you're unlikely to see much of an 1019 improvement by using 'GLSL' or 'OPENGL' as most of the 1020 time Python is slow interacting with the console and the 1021 rendering itself is pretty fast even on 'SDL'. 1022 1023 @rtype: L{Console} 1024 @return: The root console. Only what is drawn on the root console is 1025 what's visible after a call to L{tdl.flush}. 1026 After the root console is garbage collected, the window made by 1027 this function will close. 1028 """ 1029 RENDERERS = {'GLSL': 0, 'OPENGL': 1, 'SDL': 2} 1030 global _rootinitialized, _rootConsoleRef 1031 if not _fontinitialized: # set the default font to the one that comes with tdl 1032 setFont(_unpackfile('terminal8x8.png'), None, None, True, True) 1033 1034 if renderer.upper() not in RENDERERS: 1035 raise TDLError('No such render type "%s", expected one of "%s"' % (renderer, '", "'.join(RENDERERS))) 1036 renderer = RENDERERS[renderer.upper()] 1037 1038 # If a console already exists then make a clone to replace it 1039 if _rootConsoleRef and _rootConsoleRef(): 1040 oldroot = _rootConsoleRef() 1041 rootreplacement = Console(oldroot.width, oldroot.height) 1042 rootreplacement.blit(oldroot) 1043 oldroot._replace(rootreplacement) 1044 del rootreplacement 1045 1046 if title is None: # use a default title 1047 if sys.argv: 1048 # Use the script filename as the title. 1049 title = os.path.basename(sys.argv[0]) 1050 else: 1051 title = 'python-tdl' 1052 1053 _lib.TCOD_console_init_root(width, height, _encodeString(title), fullscreen, renderer) 1054 1055 #event.get() # flush the libtcod event queue to fix some issues 1056 # issues may be fixed already 1057 1058 event._eventsflushed = False 1059 _rootinitialized = True 1060 rootconsole = Console._newConsole(ctypes.c_void_p()) 1061 _rootConsoleRef = weakref.ref(rootconsole) 1062 1063 return rootconsole
1064
1065 -def flush():
1066 """Make all changes visible and update the screen. 1067 1068 Remember to call this function after drawing operations. 1069 Calls to flush will enfore the frame rate limit set by L{tdl.setFPS}. 1070 1071 This function can only be called after L{tdl.init} 1072 """ 1073 if not _rootinitialized: 1074 raise TDLError('Cannot flush without first initializing with tdl.init') 1075 1076 _lib.TCOD_console_flush()
1077
1078 -def setFont(path, columns=None, rows=None, columnFirst=False, 1079 greyscale=False, altLayout=False):
1080 """Changes the font to be used for this session. 1081 This should be called before L{tdl.init} 1082 1083 If the font specifies its size in its filename (i.e. font_NxN.png) then this 1084 function can auto-detect the tileset formatting and the parameters columns 1085 and rows can be left None. 1086 1087 While it's possible you can change the font mid program it can sometimes 1088 break in rare circumstances. So use caution when doing this. 1089 1090 @type path: string 1091 @param path: Must be a string filepath where a bmp or png file is found. 1092 1093 @type columns: int 1094 @param columns: Number of columns in the tileset. 1095 1096 Can be left None for auto-detection. 1097 1098 @type rows: int 1099 @param rows: Number of rows in the tileset. 1100 1101 Can be left None for auto-detection. 1102 1103 @type columnFirst: boolean 1104 @param columnFirst: Defines if the characer order goes along the rows or 1105 colomns. 1106 It should be True if the charater codes 0-15 are in the 1107 first column. 1108 And should be False if the characters 0-15 1109 are in the first row. 1110 1111 @type greyscale: boolean 1112 @param greyscale: Creates an anti-aliased font from a greyscale bitmap. 1113 Otherwise it uses the alpha channel for anti-aliasing. 1114 1115 Unless you actually need anti-aliasing from a font you 1116 know uses a smooth greyscale channel you should leave 1117 this on False. 1118 1119 @type altLayout: boolean 1120 @param altLayout: An alternative layout with space in the upper left 1121 corner. 1122 The colomn parameter is ignored if this is True, 1123 find examples of this layout in the font/libtcod/ 1124 directory included with the python-tdl source. 1125 1126 @raise TDLError: Will be raised if no file is found at path or if auto- 1127 detection fails. 1128 1129 @note: A png file that's been optimized can fail to load correctly on 1130 MAC OS X creating a garbled mess when rendering. 1131 Don't use a program like optipng or just use bmp files instead if 1132 you want your program to work on macs. 1133 """ 1134 # put up some constants that are only used here 1135 FONT_LAYOUT_ASCII_INCOL = 1 1136 FONT_LAYOUT_ASCII_INROW = 2 1137 FONT_TYPE_GREYSCALE = 4 1138 FONT_LAYOUT_TCOD = 8 1139 global _fontinitialized 1140 _fontinitialized = True 1141 flags = 0 1142 if altLayout: 1143 flags |= FONT_LAYOUT_TCOD 1144 elif columnFirst: 1145 flags |= FONT_LAYOUT_ASCII_INCOL 1146 else: 1147 flags |= FONT_LAYOUT_ASCII_INROW 1148 if greyscale: 1149 flags |= FONT_TYPE_GREYSCALE 1150 if not os.path.exists(path): 1151 raise TDLError('no file exists at: "%s"' % path) 1152 path = os.path.abspath(path) 1153 1154 # and the rest is the auto-detect script 1155 imgSize = _getImageSize(path) # try to find image size 1156 if imgSize: 1157 imgWidth, imgHeight = imgSize 1158 # try to get font size from filename 1159 match = re.match('.*?([0-9]+)[xX]([0-9]+)', os.path.basename(path)) 1160 if match: 1161 fontWidth, fontHeight = match.groups() 1162 fontWidth, fontHeight = int(fontWidth), int(fontHeight) 1163 1164 # estimate correct tileset size 1165 estColumns, remC = divmod(imgWidth, fontWidth) 1166 estRows, remR = divmod(imgHeight, fontHeight) 1167 if remC or remR: 1168 warnings.warn("Font may be incorrectly formatted.") 1169 1170 if not columns: 1171 columns = estColumns 1172 if not rows: 1173 rows = estRows 1174 else: 1175 # the font name excluded the fonts size 1176 if not (columns and rows): 1177 # no matched font size and no tileset is given 1178 raise TDLError('%s has no font size in filename' % os.path.basename(path)) 1179 1180 if columns and rows: 1181 # confirm user set options 1182 if (fontWidth * columns != imgWidth or 1183 fontHeight * rows != imgHeight): 1184 warnings.warn("setFont parameters are set as if the image size is (%d, %d) when the detected size is actually (%i, %i)" 1185 % (fontWidth * columns, fontHeight * rows, 1186 imgWidth, imgHeight)) 1187 else: 1188 warnings.warn("%s is probably not an image." % os.path.basename(path)) 1189 1190 if not (columns and rows): 1191 # didn't auto-detect 1192 raise TDLError('Can not auto-detect the tileset of %s' % os.path.basename(path)) 1193 1194 _lib.TCOD_console_set_custom_font(_encodeString(path), flags, columns, rows)
1195
1196 -def getFullscreen():
1197 """Returns True if program is fullscreen. 1198 1199 @rtype: boolean 1200 @return: Returns True if the window is in fullscreen mode. 1201 Otherwise returns False. 1202 """ 1203 if not _rootinitialized: 1204 raise TDLError('Initialize first with tdl.init') 1205 return _lib.TCOD_console_is_fullscreen()
1206
1207 -def setFullscreen(fullscreen):
1208 """Changes the fullscreen state. 1209 1210 @type fullscreen: boolean 1211 """ 1212 if not _rootinitialized: 1213 raise TDLError('Initialize first with tdl.init') 1214 _lib.TCOD_console_set_fullscreen(fullscreen)
1215
1216 -def setTitle(title):
1217 """Change the window title. 1218 1219 @type title: string 1220 """ 1221 if not _rootinitialized: 1222 raise TDLError('Not initilized. Set title with tdl.init') 1223 _lib.TCOD_console_set_window_title(_encodeString(title))
1224
1225 -def screenshot(path=None):
1226 """Capture the screen and save it as a png file 1227 1228 @type path: string 1229 @param path: The filepath to save the screenshot. 1230 1231 If path is None then the image will be placed in the current 1232 folder with the names: 1233 screenshot001.png, screenshot002.png, ... 1234 """ 1235 if not _rootinitialized: 1236 raise TDLError('Initialize first with tdl.init') 1237 if isinstance(fileobj, str): 1238 _lib.TCOD_sys_save_screenshot(_encodeString(fileobj)) 1239 elif isinstance(fileobj, file): # save to temp file and copy to file-like obj 1240 tmpname = os.tempnam() 1241 _lib.TCOD_sys_save_screenshot(_encodeString(tmpname)) 1242 with tmpname as tmpfile: 1243 fileobj.write(tmpfile.read()) 1244 os.remove(tmpname) 1245 elif fileobj is None: # save to screenshot001.png, screenshot002.png, ... 1246 filelist = os.listdir('.') 1247 n = 1 1248 filename = 'screenshot%.3i.png' % n 1249 while filename in filelist: 1250 n += 1 1251 filename = 'screenshot%.4i.png' % n 1252 _lib.TCOD_sys_save_screenshot(_encodeString(filename)) 1253 else: 1254 raise TypeError('fileobj is an invalid type: %s' % type(fileobj))
1255
1256 -def setFPS(frameRate):
1257 """Set the maximum frame rate. 1258 1259 @type frameRate: int 1260 @param frameRate: Further calls to L{tdl.flush} will limit the speed of 1261 the program to run at <frameRate> frames per second. Can 1262 also be set to 0 to run without a limit. 1263 1264 Defaults to None. 1265 """ 1266 if frameRate is None: 1267 frameRate = 0 1268 assert isinstance(frameRate, _INTTYPES), 'frameRate must be an integer or None, got: %s' % repr(frameRate) 1269 _lib.TCOD_sys_set_fps(frameRate)
1270
1271 -def getFPS():
1272 """Return the current frames per second of the running program set by 1273 L{setFPS} 1274 1275 @rtype: int 1276 @return: Returns the frameRate set by setFPS. 1277 If set to no limit, this will return 0. 1278 """ 1279 return _lib.TCOD_sys_get_fps()
1280
1281 -def forceResolution(width, height):
1282 """Change the fullscreen resoulution 1283 1284 @type width: int 1285 @type height: int 1286 """ 1287 _lib.TCOD_sys_force_fullscreen_resolution(width, height)
1288 1289 __all__ = [_var for _var in locals().keys() if _var[0] != '_' and _var not in ['sys', 'os', 'ctypes', 'array', 'weakref', 'itertools', 'textwrap', 'struct', 're', 'warnings']] 1290 __all__ += ['_MetaConsole'] # keep this object public to show the documentation in epydoc 1291