1 """
2 The documentation for python-untdl. A fork of tdl (A Pythonic port of
3 U{libtcod<http://doryen.eptalys.net/libtcod/>}).
4
5
6 Getting Started
7 ===============
8 Once the library is imported you can load the font you want to use with
9 L{untdl.setFont}.
10 This is optional and when skipped will use a decent default font.
11
12 After that you call L{untdl.init} to set the size of the window and get the
13 root console in return.
14 This console is the canvas to what will appear on the screen.
15
16 Indexing Consoles
17 =================
18 For most methods taking a position you can use Python-style negative
19 indexes to refer to the opposite side of a console with (-1, -1)
20 starting at the bottom right.
21 You can also check if a point is part of a console using containment
22 logic i.e. ((x, y) in console).
23
24 You may also iterate over a console using a for statement. This returns
25 every x,y coordinate available to draw on but it will be extremely slow
26 to actually operate on every coordinate individually.
27 Try to minimize draws by using an offscreen L{Console}, only drawing
28 what needs to be updated, and using L{Console.blit}.
29
30 Drawing
31 =======
32 Once you have the root console from L{untdl.init} you can start drawing on
33 it using a method such as L{Console.drawChar}.
34 When using this method you can have the char parameter be an integer or a
35 single character string.
36
37 The fgcolor and bgcolor parameters expect a three item list
38 [red, green, blue] with integers in the 0-255 range with [0, 0, 0] being
39 black and [255, 255, 255] being white.
40 Or instead you can use None in the place of any of the three parameters
41 to tell the library to not overwrite colors.
42 After the drawing functions are called a call to L{untdl.flush} will update
43 the screen.
44 """
45
46 import sys
47 import os
48
49 import ctypes
50 import weakref
51 import array
52 import itertools
53 import textwrap
54 import struct
55 import re
56 import warnings
57
58 from . import event, map, noise
59
60 from .__tcod import _lib, _Color, _unpackfile
61
62 _IS_PYTHON3 = (sys.version_info[0] == 3)
63
64 if _IS_PYTHON3:
65 _INTTYPES = (int,)
66 _NUMTYPES = (int, float)
67 _STRTYPES = (str, bytes)
68 else:
69 _INTTYPES = (int, long)
70 _NUMTYPES = (int, long, float)
71 _STRTYPES = (str,)
75 """changes string into bytes if running in python 3, for sending to ctypes"""
76 if _IS_PYTHON3 and isinstance(string, str):
77 return string.encode()
78 return string
79
99
100
101
102 _font_initialized = False
103 _root_initialized = False
104 _rootConsoleRef = None
105
106 _set_char = _lib.TCOD_console_set_char
107 _set_fore = _lib.TCOD_console_set_char_foreground
108 _set_back = _lib.TCOD_console_set_char_background
109 _set_char_ex = _lib.TCOD_console_put_char_ex
113 """Used internally.
114 Raise an assertion error if the parameters can not be converted into colors.
115 """
116 for color in colors:
117 assert _is_color(color), 'a color must be a 3 item tuple, web format, or None, received %s' % repr(color)
118 return True
119
122 """Used internally.
123 A debug function to see if an object can be used as a TCOD color struct.
124 None counts as a parameter to keep the current colors instead.
125
126 This function is often part of an inner-loop and can slow a program down.
127 It has been made to work with assert and can be skipped with the -O flag.
128 Still it's called often and must be optimized.
129 """
130 if color is None:
131 return True
132 if isinstance(color, (tuple, list, _Color)):
133 return len(color) == 3
134 if isinstance(color, _INTTYPES):
135 return True
136 return False
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 _formatColor = _Color.new
158 """Try to get the width and height of a bmp of png image file"""
159 image_file = open(filename, 'rb')
160 if image_file.read(8) == b'\x89PNG\r\n\x1a\n':
161 while 1:
162 length, = struct.unpack('>i', image_file.read(4))
163 chunk_id = image_file.read(4)
164 if chunk_id == '':
165 return None
166 if chunk_id == b'IHDR':
167
168 return struct.unpack('>ii', image_file.read(8))
169 image_file.seek(4 + length, 1)
170 image_file.seek(0)
171 if image_file.read(8) == b'BM':
172 image_file.seek(18, 0)
173
174 return struct.unpack('<ii', image_file.read(8))
175
179 """
180 The catch all for most unTDL specific errors.
181 """
182
470
521
571
572
630
647
654
663
673
674
706
707
708 def get_cover(x, length):
709 """return the (x, width) ranges of what is covered and uncovered"""
710 cover = (0, length)
711 uncover = None
712 if x > 0:
713 cover = (x, length - x)
714 uncover = (0, x)
715 elif x < 0:
716 x = abs(x)
717 cover = (0, length - x)
718 uncover = (length - x, x)
719 return cover, uncover
720
721 width, height = self.get_size()
722 if abs(x) >= width or abs(y) >= height:
723 return self.clear()
724
725
726 cover_x, uncover_x = get_cover(x, width)
727 cover_y, uncover_y = get_cover(y, height)
728
729
730
731
732
733
734
735 x, width, src_x = get_slide(x, width)
736 y, height, src_y = get_slide(y, height)
737 self.blit(self, x, y, width, height, src_x, src_y)
738
739 if uncover_x:
740 self.draw_rect(uncover_x[0], cover_y[0], uncover_x[1], cover_y[1], 0x20, 0x000000, 0x000000)
741 if uncover_y:
742 self.draw_rect(cover_x[0], uncover_y[0], cover_x[1], uncover_y[1], 0x20, 0x000000, 0x000000)
743 if uncover_x and uncover_y:
744 self.draw_rect(uncover_x[0], uncover_y[0], uncover_x[1], uncover_y[1], 0x20, 0x000000, 0x000000)
745
758
764
767 """Contains character and color data and can be drawn to.
768
769 The console created by the L{untdl.init} function is the root console and is the
770 console that is rendered to the screen with L{flush}.
771
772 Any console created from the Console class is an off-screen console that
773 can be drawn on before being L{blit} to the root console.
774 """
775
776 __slots__ = ('_as_parameter_', '_typewriter')
777
794
795
796
797 @classmethod
808
822
824
825 clone = self.__class__(self.width, self.height)
826 clone.blit(self)
827 return clone
828
834
842
857
858 @staticmethod
860 """Conversion of x and y to their position on the root Console for this Window
861
862 Because this is a Console instead of a Window we return the parameters
863 untouched"""
864 return x, y
865
866 - def clear(self, fgcolor=(0, 0, 0), bgcolor=(0, 0, 0)):
867 """Clears the entire Console.
868
869 @type fgcolor: (r, g, b)
870 @param fgcolor: Foreground color.
871
872 Must be a 3-item list with integers that range 0-255.
873
874 Unlike most other operations you cannot use None here.
875 @type bgcolor: (r, g, b)
876 @param bgcolor: Background color. See fgcolor.
877 """
878 assert _verify_colors(fgcolor, bgcolor)
879 assert fgcolor and bgcolor, 'Can not use None with clear'
880 self._typewriter = None
881 _lib.TCOD_console_set_default_background(self, _formatColor(bgcolor))
882 _lib.TCOD_console_set_default_foreground(self, _formatColor(fgcolor))
883 _lib.TCOD_console_clear(self)
884
885 - def _set_char(self, x, y, char, fgcolor=None, bgcolor=None, bgblend=1):
886 """
887 Sets a character.
888 This is called often and is designed to be as fast as possible.
889
890 Because of the need for speed this function will do NO TYPE CHECKING
891 AT ALL, it's up to the drawing functions to use the functions:
892 _format_char and _formatColor before passing to this."""
893
894 console = self._as_parameter_
895
896 if char is not None and fgcolor is not None and bgcolor is not None:
897 _set_char_ex(console, x, y, char, fgcolor, bgcolor)
898 return
899 if char is not None:
900 _set_char(console, x, y, char)
901 if fgcolor is not None:
902 _set_fore(console, x, y, fgcolor)
903 if bgcolor is not None:
904 _set_back(console, x, y, bgcolor, bgblend)
905
906 - def _set_char_batch(self, batch, fgcolor, bgcolor, bgblend=1, null_char=False):
907 """
908 Try to perform a batch operation otherwise fall back to _set_char.
909 If fgcolor and bgcolor are defined then this is faster but not by very
910 much.
911
912 batch is a iterable of [(x, y), ch] items
913 """
914 if fgcolor and not null_char:
915
916 self._typewriter = None
917 console = self._as_parameter_
918 bgblend = ctypes.c_int(bgblend)
919
920 if not bgcolor:
921 bgblend = 0
922 else:
923 _lib.TCOD_console_set_default_background(console, bgcolor)
924 _lib.TCOD_console_set_default_foreground(console, fgcolor)
925 _putChar = _lib.TCOD_console_put_char
926 for (x, y), char in batch:
927 _putChar(console, x, y, char, bgblend)
928 else:
929 for (x, y), char in batch:
930 self._set_char(x, y, char, fgcolor, bgcolor, bgblend)
931
933
934 x, y = self._normalize_point(x, y)
935 char = _lib.TCOD_console_get_char(self, x, y)
936 bgcolor = _lib.TCOD_console_get_char_background_wrapper(self, x, y)
937 fgcolor = _lib.TCOD_console_get_char_foreground_wrapper(self, x, y)
938 return char, tuple(fgcolor), tuple(bgcolor)
939
941 return "<Console (Width=%i Height=%i)>" % (self.width, self.height)
942
943
944
945 -class Window(_MetaConsole):
946 """A Window contains a small isolated part of a Console.
947
948 Drawing on the Window draws on the Console.
949
950 Making a Window and setting its width or height to None will extend it to
951 the edge of the console.
952 """
953
954 __slots__ = ('parent', 'x', 'y')
955
956 - def __init__(self, console, x, y, width, height):
957 """Isolate part of a L{Console} or L{Window} instance.
958
959 @type console: L{Console} or L{Window}
960 @param console: The parent object which can be a L{Console} or another
961 L{Window} instance.
962
963 @type x: int
964 @param x: X coordinate to place the Window.
965
966 This follows the normal rules for indexing so you can use a
967 negative integer to place the Window relative to the bottom
968 right of the parent Console instance.
969 @type y: int
970 @param y: Y coordinate to place the Window.
971
972 See x.
973
974 @type width: int or None
975 @param width: Width of the Window.
976
977 Can be None to extend as far as possible to the
978 bottom right corner of the parent Console or can be a
979 negative number to be sized relative to the Consoles total
980 size.
981 @type height: int or None
982 @param height: Height of the Window.
983
984 See width.
985 """
986 _MetaConsole.__init__(self)
987 assert isinstance(console,
988 (Console, Window)), \
989 'console parameter must be a Console or Window instance, got %s' % repr(console)
990 self.parent = console
991 self.x, self.y, self.width, self.height = console._normalize_rect(x, y, width, height)
992 if isinstance(console, Console):
993 self.console = console
994 else:
995 self.console = self.parent.console
996
998 """Conversion x and y to their position on the root Console"""
999
1000 return self.parent._translate((x + self.x), (y + self.y))
1001
1002 - def clear(self, fgcolor=(0, 0, 0), bgcolor=(0, 0, 0)):
1003 """Clears the entire Window.
1004
1005 @type fgcolor: (r, g, b)
1006 @param fgcolor: Foreground color.
1007
1008 Must be a 3-item list with integers that range 0-255.
1009
1010 Unlike most other operations you can not use None here.
1011 @type bgcolor: (r, g, b)
1012 @param bgcolor: Background color. See fgcolor.
1013 """
1014 assert _verify_colors(fgcolor, bgcolor)
1015 assert fgcolor and bgcolor, 'Can not use None with clear'
1016 self.draw_rect(0, 0, None, None, 0x20, fgcolor, bgcolor)
1017
1018 - def _set_char(self, x, y, char=None, fgcolor=None, bgcolor=None, bgblend=1):
1020
1022 my_x = self.x
1023 my_y = self.y
1024 self.parent._set_char_batch((((x + my_x, y + my_y), ch) for ((x, y), ch) in batch),
1025 fgcolor, bgcolor, bgblend)
1026
1027 - def draw_char(self, x, y, char, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
1031
1032 - def draw_rect(self, x, y, width, height, string, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
1033
1034 x, y, width, height = self._normalize_rect(x, y, width, height)
1035 self.parent.draw_rect(x + self.x, y + self.y, width, height, string, fgcolor, bgcolor)
1036
1037 - def draw_frame(self, x, y, width, height, string, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
1038
1039 x, y, width, height = self._normalize_rect(x, y, width, height)
1040 self.parent.draw_frame(x + self.x, y + self.y, width, height, string, fgcolor, bgcolor)
1041
1043
1044 x, y = self._normalize_point(x, y)
1045 trans_x, trans_y = self._translate(x, y)
1046 return self.console.get_char(trans_x, trans_y)
1047
1049 return "<Window(X=%i Y=%i Width=%i Height=%i)>" % (self.x, self.y,
1050 self.width,
1051 self.height)
1052
1053
1054
1055 -def init(width, height, title=None, fullscreen=False, renderer='SDL'):
1056 """Start the main console with the given width and height and return the
1057 root console.
1058
1059 Call the consoles drawing functions. Then remember to use L{untdl.flush} to
1060 make what's drawn visible on the console.
1061
1062 @type width: int
1063 @param width: width of the root console (in tiles)
1064
1065 @type height: int
1066 @param height: height of the root console (in tiles)
1067
1068 @type title: string
1069 @param title: Text to display as the window title.
1070
1071 If left None it defaults to the running scripts filename.
1072
1073 @type fullscreen: boolean
1074 @param fullscreen: Can be set to True to start in fullscreen mode.
1075
1076 @type renderer: string
1077 @param renderer: Can be one of 'GLSL', 'OPENGL', or 'SDL'.
1078
1079 Due to way Python works you're unlikely to see much of an
1080 improvement by using 'GLSL' or 'OPENGL' as most of the
1081 time Python is slow interacting with the console and the
1082 rendering itself is pretty fast even on 'SDL'.
1083
1084 @rtype: L{Console}
1085 @return: The root console. Only what is drawn on the root console is
1086 what's visible after a call to L{untdl.flush}.
1087 After the root console is garbage collected, the window made by
1088 this function will close.
1089 """
1090 renderers = {'GLSL': 0, 'OPENGL': 1, 'SDL': 2}
1091 global _root_initialized, _rootConsoleRef
1092 if not _font_initialized:
1093 set_font(_unpackfile('terminal8x8.png'), None, None, True, True)
1094
1095 if renderer.upper() not in renderers:
1096 raise TDLError('No such render type "%s", expected one of "%s"' % (renderer, '", "'.join(renderers)))
1097 renderer = renderers[renderer.upper()]
1098
1099
1100 if _rootConsoleRef and _rootConsoleRef():
1101 old_root = _rootConsoleRef()
1102 root_replacement = Console(old_root.width, old_root.height)
1103 root_replacement.blit(old_root)
1104 old_root._replace(root_replacement)
1105 del root_replacement
1106
1107 if title is None:
1108 if sys.argv:
1109
1110 title = os.path.basename(sys.argv[0])
1111 else:
1112 title = 'python-untdl'
1113
1114 _lib.TCOD_console_init_root(width, height, _encode_string(title), fullscreen, renderer)
1115
1116
1117
1118
1119 event._eventsflushed = False
1120 _root_initialized = True
1121 root_console = Console._new_console(ctypes.c_void_p())
1122 _rootConsoleRef = weakref.ref(root_console)
1123
1124 return root_console
1125
1128 """Make all changes visible and update the screen.
1129
1130 Remember to call this function after drawing operations.
1131 Calls to flush will enforce the frame rate limit set by L{untdl.set_fps}.
1132
1133 This function can only be called after L{untdl.init}
1134 """
1135 if not _root_initialized:
1136 raise TDLError('Cannot flush without first initializing with untdl.init')
1137
1138 _lib.TCOD_console_flush()
1139
1140
1141
1142 -def set_font(path, columns=None, rows=None, column_first=False,
1143 greyscale=False, alt_layout=False):
1144 """Changes the font to be used for this session.
1145 This should be called before L{untdl.init}
1146
1147 If the font specifies its size in its filename (i.e. font_NxN.png) then this
1148 function can auto-detect the tileset formatting and the parameters columns
1149 and rows can be left None.
1150
1151 While it's possible you can change the font mid program it can sometimes
1152 break in rare circumstances. So use caution when doing this.
1153
1154 @type path: string
1155 @param path: Must be a string file path where a bmp or png file is found.
1156
1157 @type columns: int
1158 @param columns: Number of columns in the tileset.
1159
1160 Can be left None for auto-detection.
1161
1162 @type rows: int
1163 @param rows: Number of rows in the tileset.
1164
1165 Can be left None for auto-detection.
1166
1167 @type column_first: boolean
1168 @param column_first: Defines if the character order goes along the rows or
1169 columns.
1170 It should be True if the character codes 0-15 are in the
1171 first column.
1172 And should be False if the characters 0-15
1173 are in the first row.
1174
1175 @type greyscale: boolean
1176 @param greyscale: Creates an anti-aliased font from a greyscale bitmap.
1177 Otherwise it uses the alpha channel for anti-aliasing.
1178
1179 Unless you actually need anti-aliasing from a font you
1180 know uses a smooth greyscale channel you should leave
1181 this on False.
1182
1183 @type alt_layout: boolean
1184 @param alt_layout: An alternative layout with space in the upper left
1185 corner.
1186 The column parameter is ignored if this is True,
1187 find examples of this layout in the font/libtcod/
1188 directory included with the python-untdl source.
1189
1190 @raise TDLError: Will be raised if no file is found at path or if auto-
1191 detection fails.
1192
1193 @note: A png file that's been optimized can fail to load correctly on
1194 MAC OS X creating a garbled mess when rendering.
1195 Don't use a program like optipng or just use bmp files instead if
1196 you want your program to work on macs.
1197 """
1198
1199 FONT_LAYOUT_ASCII_INCOL = 1
1200 FONT_LAYOUT_ASCII_INROW = 2
1201 FONT_TYPE_GREYSCALE = 4
1202 FONT_LAYOUT_TCOD = 8
1203 global _font_initialized
1204 _font_initialized = True
1205 flags = 0
1206 if alt_layout:
1207 flags |= FONT_LAYOUT_TCOD
1208 elif column_first:
1209 flags |= FONT_LAYOUT_ASCII_INCOL
1210 else:
1211 flags |= FONT_LAYOUT_ASCII_INROW
1212 if greyscale:
1213 flags |= FONT_TYPE_GREYSCALE
1214 if not os.path.exists(path):
1215 raise TDLError('no file exists at: "%s"' % path)
1216 path = os.path.abspath(path)
1217
1218
1219 imgSize = _get_image_size(path)
1220 if imgSize:
1221 imgWidth, imgHeight = imgSize
1222
1223 match = re.match('.*?([0-9]+)[xX]([0-9]+)', os.path.basename(path))
1224 if match:
1225 font_width, font_height = match.groups()
1226 font_width, font_height = int(font_width), int(font_height)
1227
1228
1229 estColumns, remC = divmod(imgWidth, font_width)
1230 estRows, remR = divmod(imgHeight, font_height)
1231 if remC or remR:
1232 warnings.warn("Font may be incorrectly formatted.")
1233
1234 if not columns:
1235 columns = estColumns
1236 if not rows:
1237 rows = estRows
1238 else:
1239
1240 if not (columns and rows):
1241
1242 raise TDLError('%s has no font size in filename' % os.path.basename(path))
1243
1244 if columns and rows:
1245
1246 if font_width * columns != imgWidth or font_height * rows != imgHeight:
1247 warnings.warn(
1248 "font parameters are set as if the image size is (%d, %d) when the detected size is (%i, %i)"
1249 % (font_width * columns, font_height * rows,
1250 imgWidth, imgHeight))
1251 else:
1252 warnings.warn("%s is probably not an image." % os.path.basename(path))
1253
1254 if not (columns and rows):
1255
1256 raise TDLError('Can not auto-detect the tileset of %s' % os.path.basename(path))
1257
1258 _lib.TCOD_console_set_custom_font(_encode_string(path), flags, columns, rows)
1259
1262 """Maps characters in the bitmap font to ASCII codes.
1263 This should be called after L{untdl.init}
1264
1265 You can dynamically change the characters mapping at any time, allowing to use several fonts in the same screen.
1266
1267 @type first_ascii_code: int
1268 @param first_ascii_code: First ascii code to map.
1269
1270 @type num_codes: int
1271 @param num_codes: Number of consecutive ascii codes to map.
1272
1273 @type font_char_x: int
1274 @param font_char_x: Coordinate of the character in the bitmap font (in characters, not pixels)
1275 corresponding to the first ASCII code.
1276
1277 @type font_char_y: int
1278 @param font_char_y: Coordinate of the character in the bitmap font (in characters, not pixels)
1279 corresponding to the first ASCII code.
1280
1281 @rtype: None
1282 """
1283 _lib.TCOD_console_map_ascii_codes_to_font(first_ascii_code, num_codes, font_char_x, font_char_y)
1284
1287 """Returns True if program is fullscreen.
1288
1289 @rtype: boolean
1290 @return: Returns True if the window is in fullscreen mode.
1291 Otherwise returns False.
1292 """
1293 if not _root_initialized:
1294 raise TDLError('Initialize first with untdl.init')
1295 return _lib.TCOD_console_is_fullscreen()
1296
1299 """Changes the fullscreen state.
1300
1301 @type fullscreen: boolean
1302 """
1303 if not _root_initialized:
1304 raise TDLError('Initialize first with untdl.init')
1305 _lib.TCOD_console_set_fullscreen(fullscreen)
1306
1309 """Change the window title.
1310
1311 @type title: string
1312 """
1313 if not _root_initialized:
1314 raise TDLError('Not initilized. Set title with untdl.init')
1315 _lib.TCOD_console_set_window_title(_encode_string(title))
1316
1319 """Capture the screen and save it as a png file
1320
1321 @type path: string
1322 @param path: The file path to save the screenshot.
1323
1324 If path is None then the image will be placed in the current
1325 folder with the names:
1326 screenshot001.png, screenshot002.png, ...
1327 """
1328 if not _root_initialized:
1329 raise TDLError('Initialize first with untdl.init')
1330 if isinstance(path, str):
1331 _lib.TCOD_sys_save_screenshot(_encode_string(path))
1332 elif path is None:
1333 file_list = os.listdir('.')
1334 n = 1
1335 filename = 'screenshot%.3i.png' % n
1336 while filename in file_list:
1337 n += 1
1338 filename = 'screenshot%.3i.png' % n
1339 _lib.TCOD_sys_save_screenshot(_encode_string(filename))
1340 else:
1341
1342 tmp_name = os.tempnam()
1343 _lib.TCOD_sys_save_screenshot(_encode_string(tmp_name))
1344 with tmp_name as tmp_file:
1345 path.write(tmp_file.read())
1346 os.remove(tmp_name)
1347
1348
1349
1350
1351 -def set_fps(frame_rate):
1352 """Set the maximum frame rate.
1353
1354 @type frame_rate: int
1355 @param frame_rate: Further calls to L{untdl.flush} will limit the speed of
1356 the program to run at <frame_rate> frames per second. Can
1357 also be set to 0 to run without a limit.
1358
1359 Defaults to None.
1360 """
1361 if frame_rate is None:
1362 frame_rate = 0
1363 assert isinstance(frame_rate, _INTTYPES), 'frame_rate must be an integer or None, got: %s' % repr(frame_rate)
1364 _lib.TCOD_sys_set_fps(frame_rate)
1365
1368 """Return the current frames per second of the running program set by
1369 L{set_fps}
1370
1371 @rtype: int
1372 @return: Returns the frameRate set by set_fps.
1373 If set to no limit, this will return 0.
1374 """
1375 return _lib.TCOD_sys_get_fps()
1376
1379 """Change the fullscreen resolution
1380
1381 @type width: int
1382 @type height: int
1383 """
1384 _lib.TCOD_sys_force_fullscreen_resolution(width, height)
1385
1386
1387 __all__ = [_var for _var in locals().keys() if _var[0] != '_' and _var not in
1388 ['sys', 'os', 'ctypes', 'array', 'weakref', 'itertools', 'textwrap',
1389 'struct', 're', 'warnings']]
1390 __all__ += ['_MetaConsole']
1391
1392 __license__ = "New BSD License"
1393 __email__ = "4b796c65+pythonTDL@gmail.com"
1394
1395 file = open(os.path.join(os.path.dirname(__file__), 'VERSION.txt'), 'r')
1396 __version__ = file.read()
1397 file.close()
1398