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:
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,)
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
100
101
102 _fontinitialized = False
103 _rootinitialized = False
104 _rootConsoleRef = None
105
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
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
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151 _formatColor = _Color.new
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':
157 while 1:
158 length, = struct.unpack('>i', file.read(4))
159 chunkID = file.read(4)
160 if chunkID == '':
161 return None
162 if chunkID == b'IHDR':
163
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':
168 file.seek(18, 0)
169
170 return struct.unpack('<ii', file.read(8))
171
174 """
175 The catch all for most TDL specific errors.
176 """
177
460
511
561
618
635
642
651
661
691 def getCover(x, length):
692 """return the (x, width) ranges of what is covered and uncovered"""
693 cover = (0, length)
694 uncover = None
695 if x > 0:
696 cover = (x, length - x)
697 uncover = (0, x)
698 elif x < 0:
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()
707
708
709 coverX, uncoverX = getCover(x, width)
710 coverY, uncoverY = getCover(y, height)
711
712
713
714
715
716
717
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:
723 self.drawRect(uncoverX[0], coverY[0], uncoverX[1], coverY[1], 0x20, 0x000000, 0x000000)
724 if uncoverY:
725 self.drawRect(coverX[0], uncoverY[0], coverX[1], uncoverY[1], 0x20, 0x000000, 0x000000)
726 if uncoverX and uncoverY:
727 self.drawRect(uncoverX[0], uncoverY[0], uncoverX[1], uncoverY[1], 0x20, 0x000000, 0x000000)
728
741
747
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
776
777
778 @classmethod
789
803
805
806 clone = self.__class__(self.width, self.height)
807 clone.blit(self)
808 return clone
809
815
823
838
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
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
896 self._typewriter = None
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
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
913
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
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
975 """Convertion x and y to their position on the root Console"""
976
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
999 myX = self.x
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)):
1009
1010 - def drawRect(self, x, y, width, height, string, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
1011
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
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
1024
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:
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
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:
1084 if sys.argv:
1085
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
1093
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
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
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
1192 imgSize = _getImageSize(path)
1193 if imgSize:
1194 imgWidth, imgHeight = imgSize
1195
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
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
1213 if not (columns and rows):
1214
1215 raise TDLError('%s has no font size in filename' % os.path.basename(path))
1216
1217 if columns and rows:
1218
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
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
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
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
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
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:
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:
1285
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
1292
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
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
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']]
1330 __all__ += ['_MetaConsole']
1331
1332 __license__ = "New BSD License"
1333 __email__ = "4b796c65+pythonTDL@gmail.com"
1334