Skip to content

Commit 03e775a

Browse files
4B796C65@gmail.com4B796C65@gmail.com
authored andcommitted
You can now use Python-style negative indexes when drawing.
1 parent 8bbf253 commit 03e775a

File tree

3 files changed

+83
-99
lines changed

3 files changed

+83
-99
lines changed

tdl/__init__.py

Lines changed: 71 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@
3636

3737
_IS_PYTHON3 = (sys.version_info[0] == 3)
3838

39+
if _IS_PYTHON3: # some type lists to use with isinstance
40+
_INTTYPES = (int,)
41+
_NUMTYPES = (int, float)
42+
_STRTYPES = (str, bytes)
43+
else:
44+
_INTTYPES = (int, long)
45+
_NUMTYPES = (int, long, float)
46+
_STRTYPES = (str,)
47+
3948
def _encodeString(string): # still used for filepaths, and that's about it
4049
"changes string into bytes if running in python 3, for sending to ctypes"
4150
if _IS_PYTHON3 and isinstance(string, str):
@@ -54,9 +63,9 @@ def _formatChar(char):
5463
"""
5564
if char is None:
5665
return None
57-
if isinstance(char, int) or not _IS_PYTHON3 and isinstance(char, long):
66+
if isinstance(char, _INTTYPES):
5867
return char
59-
if isinstance(char, (str, bytes)) and len(char) == 1:
68+
if isinstance(char, _STRTYPES) and len(char) == 1:
6069
return ord(char)
6170
raise TypeError('Expected char parameter to be a single character string, number, or None, got: %s' % repr(char))
6271

@@ -89,7 +98,7 @@ def _iscolor(color):
8998
return True
9099
if isinstance(color, (tuple, list, _Color)):
91100
return len(color) == 3
92-
if isinstance(color, int) or not _IS_PYTHON3 and isinstance(color, long):
101+
if isinstance(color, _INTTYPES):
93102
return True
94103
return False
95104

@@ -100,7 +109,7 @@ def _formatColor(color):
100109
return None
101110
if isinstance(color, _Color):
102111
return color
103-
if isinstance(color, int) or not _IS_PYTHON3 and isinstance(color, long):
112+
if isinstance(color, _INTTYPES):
104113
# format a web style color with the format 0xRRGGBB
105114
return _Color(color >> 16 & 0xff, color >> 8 & 0xff, color & 0xff)
106115
return _Color(*color)
@@ -139,7 +148,7 @@ def drawChar(self, x, y, char, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
139148
"""
140149

141150
assert _verify_colors(fgcolor, bgcolor)
142-
assert self._drawable(x, y)
151+
x, y = self._normalizePoint(x, y)
143152

144153
self._setChar(ctypes.c_int(x), ctypes.c_int(y), _formatChar(char),
145154
_formatColor(fgcolor), _formatColor(bgcolor))
@@ -166,7 +175,7 @@ def drawStr(self, x, y, string, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
166175
167176
"""
168177

169-
assert self._drawable(x, y)
178+
x, y = self._normalizePoint(x, y)
170179
assert _verify_colors(fgcolor, bgcolor)
171180
fgcolor, bgcolor = _formatColor(fgcolor), _formatColor(bgcolor)
172181
width, height = self.getSize()
@@ -237,64 +246,50 @@ def drawFrame(self, x, y, width, height, string, fgcolor=(255, 255, 255), bgcolo
237246
self.drawRect(x + width - 1, y, 1, height, char, fgcolor, bgcolor)
238247
self.drawRect(x, y + height - 1, width, 1, char, fgcolor, bgcolor)
239248

249+
def _normalizePoint(self, x, y):
250+
"""Check if a point is in bounds and make minor adjustments.
251+
252+
Respects Pythons negative indexes. -1 starts at the bottom right.
253+
Replaces the _drawable function
254+
"""
255+
assert isinstance(x, _INTTYPES), 'x must be an integer, got %s' % repr(x)
256+
assert isinstance(y, _INTTYPES), 'y must be an integer, got %s' % repr(y)
257+
258+
assert (-self.width <= x < self.width) and (-self.height <= y < self.height), \
259+
('(%i, %i) is an invalid postition on %s' % (x, y, self))
260+
261+
# handle negative indexes
262+
if x < 0:
263+
x += self.width
264+
if y < 0:
265+
y += self.height
266+
return (x, y)
240267

241268
def _normalizeRect(self, x, y, width, height):
242269
"""Check if the rectangle is in bounds and make minor adjustments.
243270
raise AssertionError's for any problems
244271
"""
245-
old = x, y, width, height
246-
assert isinstance(x, int), 'x must be an integer, got %s' % repr(x)
247-
assert isinstance(y, int), 'y must be an integer, got %s' % repr(y)
248-
if width == None: # if width or height are None then extend them to the edge
249-
width = self.width - x
250-
if height == None:
251-
height = self.height - y
252-
assert isinstance(width, int), 'width must be an integer or None, got %s' % repr(width)
253-
assert isinstance(height, int), 'height must be an integer or None, got %s' % repr(height)
254-
255-
assert width >= 0 and height >= 0, 'width and height cannot be negative'
256-
# later idea, negative numbers work like Python list indexing
257-
258-
assert x >= 0 and y >= 0 and x + width <= self.width and y + height <= self.height, \
259-
'Rect is out of bounds at (x=%i y=%i width=%i height=%i), Console bounds are (width=%i, height=%i)' % (old + self.getSize())
260-
return x, y, width, height
261-
262-
def _rectInBounds(self, x, y, width, height):
263-
"check the rect so see if it's within the bounds of this console"
264-
if width is None:
265-
width = 0
266-
if height is None:
267-
height = 0
268-
if (x < 0 or y < 0 or
269-
x + width > self.width or y + height > self.height):
270-
return False
271-
272-
return True
273-
274-
def _clampRect(self, x=0, y=0, width=None, height=None):
275-
"""Alter a rectange to fit inside of this console
276-
277-
width and hight of None will extend to the edge and
278-
an area out of bounds will end with a width and height of 0
279-
"""
280-
# extend any width and height of None to the end of the console
272+
x, y = self._normalizePoint(x, y) # inherit _normalizePoint logic
273+
274+
assert width is None or isinstance(width, _INTTYPES), 'width must be an integer or None, got %s' % repr(width)
275+
assert height is None or isinstance(height, _INTTYPES), 'height must be an integer or None, got %s' % repr(height)
276+
277+
# if width or height are None then extend them to the edge
281278
if width is None:
282279
width = self.width - x
280+
elif width < 0: # handle negative numbers
281+
width += self.width
282+
width = max(0, width) # a 'too big' negative is clamped zero
283283
if height is None:
284284
height = self.height - y
285-
# move x and y within bounds, shrinking the width and height to match
286-
if x < 0:
287-
width += x
288-
x = 0
289-
if y < 0:
290-
height += y
291-
y = 0
292-
# move width and height within bounds
285+
height = max(0, height)
286+
elif height < 0:
287+
height += self.height
288+
289+
# reduce rect size to bounds
293290
width = min(width, self.width - x)
294291
height = min(height, self.height - y)
295-
# a rect that was out of bounds will have a 0 or negative width or height at this point
296-
if width <= 0 or height <= 0:
297-
width = height = 0
292+
298293
return x, y, width, height
299294

300295
def blit(self, source, x=0, y=0, width=None, height=None, srcX=0, srcY=0):
@@ -316,26 +311,17 @@ def blit(self, source, x=0, y=0, width=None, height=None, srcX=0, srcY=0):
316311

317312
assert isinstance(source, (Console, Window)), "source muse be a Window or Console instance"
318313

319-
assert width is None or isinstance(width, (int)), "width must be a number or None, got %s" % repr(width)
320-
assert height is None or isinstance(height, (int)), "height must be a number or None, got %s" % repr(height)
321-
322-
# fill in width, height
323-
if width == None:
324-
width = min(self.width - x, source.width - srcX)
325-
if height == None:
326-
height = min(self.height - y, source.height - srcY)
327-
314+
# handle negative indexes and rects
315+
# negative width and height will be set realtive to the destination
316+
# and will also be clamped to the smallest Console
328317
x, y, width, height = self._normalizeRect(x, y, width, height)
329318
srcX, srcY, width, height = source._normalizeRect(srcX, srcY, width, height)
330319

331320
# translate source and self if any of them are Window instances
332-
if isinstance(source, Window):
333-
srcX, srcY = source._translate(srcX, srcY)
334-
source = source.console
335-
336-
if isinstance(self, Window):
337-
x, y = self._translate(x, y)
338-
self = self.console
321+
srcX, srcY = source._translate(srcX, srcY)
322+
source = source.console
323+
x, y = self._translate(x, y)
324+
self = self.console
339325

340326
if self == source:
341327
# if we are the same console then we need a third console to hold
@@ -361,8 +347,8 @@ def scroll(self, x, y):
361347
@type x: int
362348
@type y: int
363349
"""
364-
assert isinstance(x, int), "x must be an integer, got %s" % repr(x)
365-
assert isinstance(y, int), "y must be an integer, got %s" % repr(x)
350+
assert isinstance(x, _INTTYPES), "x must be an integer, got %s" % repr(x)
351+
assert isinstance(y, _INTTYPES), "y must be an integer, got %s" % repr(x)
366352
def getSlide(x, length):
367353
"""get the parameters needed to scroll the console in the given
368354
direction with x
@@ -422,19 +408,6 @@ def __contains__(self, position):
422408
x, y = position
423409
return (0 <= x < self.width) and (0 <= y < self.height)
424410

425-
def _drawable(self, x, y):
426-
"""Used internally
427-
Checks if a cell is part of the console.
428-
Raises an AssertionError if it can not be used.
429-
"""
430-
assert isinstance(x, int), 'x must be an integer, got %s' % repr(x)
431-
assert isinstance(y, int), 'y must be an integer, got %s' % repr(y)
432-
433-
assert (0 <= x < self.width) and (0 <= y < self.height), \
434-
('(%i, %i) is an invalid postition. %s size is (%i, %i)' %
435-
(x, y, self.__class__.__name__, self.width, self.height))
436-
return True
437-
438411
class Console(_MetaConsole):
439412
"""The Console is the main class of the tdl library.
440413
@@ -453,16 +426,18 @@ def __init__(self, width, height):
453426
if not _rootinitialized:
454427
raise TDLError('Can not create Console\'s before tdl.init')
455428
self._as_parameter_ = _lib.TCOD_console_new(width, height)
429+
self.console = self
456430
self.width = width
457431
self.height = height
458-
self._typewriter = None # "typewriter" lock, makes sure the colors are set to the typewriter
432+
self._typewriter = None # "typewriter lock", makes sure the colors are set to the typewriter
459433
#self.clear()
460434

461435
@classmethod
462436
def _newConsole(cls, console):
463437
"""Make a Console instance, from a console ctype"""
464438
self = cls.__new__(cls)
465439
self._as_parameter_ = console
440+
self.console = self
466441
self.width = _lib.TCOD_console_get_width(self)
467442
self.height = _lib.TCOD_console_get_height(self)
468443
self._typewriter = None
@@ -549,13 +524,13 @@ def _setCharBatch(self, batch, fgcolor, bgcolor, bgblend=1, nullChar=False):
549524
"""
550525
if fgcolor and bgcolor and not nullChar:
551526
# buffer values as ctypes objects
552-
self._typewriter = None
527+
self._typewriter = None # clear the typewriter as colors will be set
553528
console = self._as_parameter_
554529
bgblend = ctypes.c_int(bgblend)
555530

556531
_lib.TCOD_console_set_default_background(console, bgcolor)
557532
_lib.TCOD_console_set_default_foreground(console, fgcolor)
558-
_putChar = _lib.TCOD_console_put_char # remove dots
533+
_putChar = _lib.TCOD_console_put_char # remove dots and make local
559534
for (x, y), char in batch:
560535
_putChar(console, x, y, char, bgblend)
561536
else:
@@ -571,7 +546,8 @@ def getChar(self, x, y):
571546
572547
@rtype: (int, 3-item tuple, 3-item tuple)
573548
"""
574-
assert self._drawable(x, y)
549+
#assert self._drawable(x, y)
550+
x, y = self._normalizePoint(x, y)
575551
char = _lib.TCOD_console_get_char(self, x, y)
576552
bgcolor = _lib.TCOD_console_get_char_background_wrapper(self, x, y)
577553
fgcolor = _lib.TCOD_console_get_char_foreground_wrapper(self, x, y)
@@ -627,7 +603,7 @@ def _setCharBatch(self, batch, fgcolor, bgcolor, bgblend=1):
627603

628604

629605
def drawChar(self, x, y, char, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
630-
assert self._drawable(x, y)
606+
x, y = self._normalizePoint(x, y)
631607
self.parent.drawChar(x + self.x, y + self.y, char, fgcolor, bgcolor)
632608

633609
def drawRect(self, x, y, width, height, string, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
@@ -643,7 +619,7 @@ def getChar(self, x, y):
643619
644620
@rtype: (int, 3-item tuple, 3-item tuple)
645621
"""
646-
self._drawable(x, y)
622+
x, y = self._normalizePoint(x, y)
647623
return self.console.getChar(self._translate(x, y))
648624

649625
def __repr__(self):
@@ -695,8 +671,7 @@ def getCursor(self):
695671
return x, y
696672

697673
def move(self, x, y):
698-
assert self.parent._drawable(x, y)
699-
self.cursor = (x, y)
674+
self.cursor = self.parent._normalizePoint(x, y)
700675

701676
def setFG(self, color):
702677
assert _iscolor(color)
@@ -711,6 +686,7 @@ def setBG(self, color):
711686
_lib.TCOD_console_set_default_background(self.console, self.bgcolor)
712687

713688
def _updateConsole(self):
689+
"""Make sure the colors on a console match the Typewriter instance"""
714690
if self.console._typewriter is not self:
715691
self.console._typewriter = self
716692

@@ -726,8 +702,9 @@ def addChar(self, char):
726702
_lib.TCOD_console_put_char(self.console._as_parameter_, x, y, _formatChar(char), self._bgblend)
727703

728704

729-
def addStr(self, string, lineBreak='\n'):
705+
def addStr(self, string):
730706
x, y = self.cursor
707+
lineBreak = '\n'
731708
for char in string:
732709
if char == lineBreak:
733710
x = 0
@@ -967,7 +944,7 @@ def setFPS(frameRate):
967944
"""
968945
if frameRate is None:
969946
frameRate = 0
970-
assert isinstance(frameRate, int), 'frameRate must be an integer or None, got: %s' % repr(frameRate)
947+
assert isinstance(frameRate, _INTTYPES), 'frameRate must be an integer or None, got: %s' % repr(frameRate)
971948
_lib.TCOD_sys_set_fps(frameRate)
972949

973950
def getFPS():

tdl/fov.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def computeFOV(self, x, y, fov='PERMISSIVE', radius=8, lightWalls=True):
6969
elif fov[:10] == 'PERMISSIVE' and fov[10].isdigit():
7070
fov = 4 + int(fov[10])
7171
else:
72-
raise TDLError('No such fov as %s' % oldFOV)
72+
raise TDLError('No such fov option as %s' % oldFOV)
7373

7474
self._updateMap(x, y, radius)
7575
_lib.TCOD_map_compute_fov(self, x, y, radius, lightWalls, fov)

tdl/path.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def newCallback(fromX, fromY, toX, toY, null):
1717
self.callback, None, diagnalCost)
1818

1919
@classmethod
20-
def newFromMap(cls, map, diagnalCost=math.sqrt(2)):
20+
def FromMap(cls, map, diagnalCost=math.sqrt(2)):
2121
self = cls.__new__(cls)
2222
self.callback = None
2323
self._as_parameter_ = _lib.TCOD_path_new_using_map(map, diagnalCost)
@@ -46,15 +46,22 @@ def __init__(self, width, height, callback, diagnalCost=math.sqrt(2)):
4646
def newCallback(fromX, fromY, toX, toY, null):
4747
pathCost = callback(toX, toY) # expecting a float or 0
4848
return pathCost
49-
self.callback = newCallback
49+
self.callback = _PATHCALL(newCallback)
5050
self._as_parameter_ = _lib.TCOD_dijkstra_new_using_function(width, height,
51-
_PATHCALL(newCallback), None, diagnalCost)
51+
self.callback, None, diagnalCost)
5252
# add code to compute here with x,y
53+
54+
@classmethod
55+
def FromMap(cls, map, diagnalCost=math.sqrt(2)):
56+
self = cls.__new__(cls)
57+
self.callback = None
58+
self._as_parameter_ = _lib.TCOD_dijkstra_new_using_map(map, diagnalCost)
59+
return self
5360

5461
def __del__(self):
5562
_lib.TCOD_dijkstra_delete(self)
5663

57-
def setPos(self, x, y):
64+
def setPole(self, x, y):
5865
self.x, self.y = x, y
5966
_lib.TCOD_dijkstra_compute(self, x, y)
6067

0 commit comments

Comments
 (0)