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