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